diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 4dbe22ade..a596c7862 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -14,7 +14,7 @@ jobs: runs-on: macos-latest-large strategy: matrix: - python-version: [ "3.9", "3.10", "3.11", "3.12" ] + python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index c55590479..ce6d55f07 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -14,7 +14,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python-version: [ "3.9", "3.10", "3.11", "3.12" ] + python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 802cddf0b..21ea85b42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ "3.9", "3.10", "3.11", "3.12" ] + python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] steps: - uses: actions/checkout@v4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1a4f6ba5..282dc5ff7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ Pull requests should be done on the `dev` branch. When the release is finalised, Whether you're interested in contributing to the repository, creating a fork, or just improving your understanding of Writer Framework, these are the suggested steps for setting up a development environment. -- You can install the package in editable mode using `poetry install` +- You can install the package in editable mode using `poetry install --with build` - Enable the virtual environment with `poetry shell` - Install all the dev dependencies with `alfred install.dev` - Run Writer Framework on port 5000. For example, `writer edit apps/hello --port 5000`. diff --git a/alfred/install.py b/alfred/install.py index 8391f4933..6b79af525 100644 --- a/alfred/install.py +++ b/alfred/install.py @@ -5,9 +5,9 @@ def install_dev(): alfred.run("poetry install --with build") alfred.run("npm ci") - alfred.invoke_command("npm.codegen") + alfred.run("npm run build") @alfred.command("install.ci", help="install ci dependencies and generate code", hidden=True) def install_ci(): alfred.run("npm ci") - alfred.invoke_command("npm.codegen") + alfred.run("npm run build") diff --git a/docs/framework/ai-module.mdx b/docs/framework/ai-module.mdx index aab86f566..72cbc5703 100644 --- a/docs/framework/ai-module.mdx +++ b/docs/framework/ai-module.mdx @@ -217,7 +217,7 @@ Function tools require the following properties: When a conversation involves a tool (either a graph or a function), Framework automatically handles the requests from LLM to use the tools during interactions. If the tool needs multiple steps (for example, querying data and processing it), Framework will handle those steps recursively, calling functions as needed until the final result is returned. -By default, to prevent endless recursion, Framework will only handle 3 consecutive tool calls. You can expand it in case it doesn't suit your case – both `complete()` and `stream_complete()` accept a `max_tool_depth` parameter, which configures the maximum allowed recursion depth: +By default, to prevent endless recursion, Framework will only handle 5 consecutive tool calls. You can expand it in case it doesn't suit your case – both `complete()` and `stream_complete()` accept a `max_tool_depth` parameter, which configures the maximum allowed recursion depth: ```python response = conversation.complete(tools=tool, max_tool_depth=7) @@ -340,3 +340,84 @@ for chunk in stream_ask( ``` +## Using the `Tools` class + + +This document outlines the use of `Tools` in the Writer Framework; for more thorough documentation, check out (this guide)[https://dev.writer.com/api-guides/tools]. + + +The `writer.ai.tools` instance provides access to Writer SDK `tools` resources, such as text splitting, medical content comprehension, and PDF parsing. Below is a guide on how to use each method. + +### Splitting Content + +The `split` method divides text into chunks based on a selected strategy. + +```python +from writer.ai import tools + +content = \ + """ + This is a long piece of text that needs to be split into smaller parts. + Lorem ipsum dolor sit amet... + """ +chunks = tools.split(content, strategy="llm_split") +print(chunks) +``` + +**Parameters**: +- `content` (str): The text to be split. +- `strategy` (str): The splitting strategy (`llm_split`, `fast_split`, or `hybrid_split`). + +**Returns**: +A list of text chunks. + +### Medical Content Comprehension + +The `comprehend_medical` method processes medical text and extracts relevant entities based on a specified response type. + +```python +from writer.ai import tools + +medical_text = "Patient shows symptoms of hypertension and diabetes." +entities = tools.comprehend_medical(medical_text, response_type="Entities") +print(entities) +``` + +**Parameters**: +- `content` (str): The medical text to process. +- `response_type` (str): The type of medical response (`Entities`, `RxNorm`, `ICD-10-CM`, or `SNOMED CT`). + +**Returns**: +A list of extracted medical entities. + +### PDF Parsing + +The `parse_pdf` method extracts text content from a PDF file. The file can be referenced by its ID, or provided as a `File` object. + + +```python file_id +from writer.ai import tools + +file_id = "example-file-id" +parsed_content = tools.parse_pdf(file_id, format="text") +print(parsed_content) +``` +```python upload_file +from writer.ai import tools + +file_object = upload_file( + data=pdf_content, + type="application/pdf", + name="uploaded_file.pdf" +) +parsed_content = tools.parse_pdf(file_object, format="text") +print(parsed_content) +``` + + +**Parameters**: +- `file_id_or_file` (str or File): The file to parse (by ID or as an object). +- `format` (str): The format of the extracted content (`text` or `markdown`). + +**Returns**: +The text content of the PDF. \ No newline at end of file diff --git a/docs/framework/builder-basics.mdx b/docs/framework/builder-basics.mdx index 6c215d857..8b5f97151 100644 --- a/docs/framework/builder-basics.mdx +++ b/docs/framework/builder-basics.mdx @@ -2,134 +2,178 @@ title: "Builder basics" --- -Framework Builder works as an overlay of the running app; you edit your app while it's running. It gives you an accurate representation of what the app will look like and how it'll behave, without the need to constantly preview it. Changes to the user interface are automatically saved into `.wf/` folders. +Writer Framework Builder’s interface is an overlay on top of the running app, which allows you to edit your app while it’s running. This approach gives you an accurate representation of how the app will look and behave without the need to constantly preview it. -## Modes +## Writer Framework Builder’s modes -You can switch modes between _User Interface_, _Code_ and _Preview_ using the buttons on the top bar. +Writer Framework Builder has two modes: -### User Interface +1. **UI mode:** This is the default mode, which provides an overlay of tools for building and editing the user interface. The app still runs in UI mode, allowing to you test its functionality while you’re building it. You can also edit the app’s code and view its log. -![Framework Builder - Mode: User Interface](/framework/images/builder-basics.ui.png) +![Writer Framework Builder in UI mode](/framework/images/builder-basics.ui-mode.png) -The default mode. Allows you to focus on building the interface. +2. **Preview mode:** This mode lets you preview the application, experiencing it almost as your end users would. The tool overlay is hidden, but you can still edit the app’s code and view its log in this mode. -### Code +![Writer Framework Builder in Preview mode](/framework/images/builder-basics.preview-mode.png) -![Framework Builder - Mode: Code](/framework/images/builder-basics.code.png) +You can switch between UI mode and Preview mode by using the **UI**/**Preview** selector near the upper left corner of the page. -This mode displays the **code editor** and the **application log**, while still allowing you to access the _Component Tree_ and _Settings_. +![Writer Framework Builder’s UI/Preview selector](/framework/images/builder-basics.ui-preview-selector.png) - - - - Code changes are automatically detected. The application will reload whenever a change to a `.py` file inside the app folder is detected. This feature only works in Framework Builder i.e. `edit` mode, not when running the app in `run` mode. - - The built-in code editor for `main.py`, the entry point of your application. This editor is provided for convenience and is ideal for quick edits — but you don't need to rely on it. If you need a more powerful editor or if your codebase is distributed across several files, use a local editor. - - - Exceptions raised by your application are shown here, as log entries. Standard output from your application is also captured and displayed as log entries. - - +## Building your app in UI mode + +### The different areas of UI mode + +In UI mode, Writer Framework Builder’s page is divided into these areas: + +![Writer Framework Builder in UI mode, with indivdual areas labeled](/framework/images/builder-basics.ui-mode-with-labels.png) + +1. **Canvas:** This is where you lay out components to build your app’s user interface. It displays the UI as your users will see it. +2. **Core toolkit:** A “palette” of UI components that can be added to your app’s user interface. To add a component to your app’s UI, drag it onto the Canvas or into the Component tree. +3. **Component tree:** This provides an alternative way to view your app’s user interface: as a hierarchical tree structure. It’s useful for ensuring that UI components are located in the right place or in the correct container object, and is handy for selecting UI components in complex layouts. +4. **Component settings:** This panel lets you view and edit the settings for the currently selected UI component. You can hide the Component settings panel if you need more screen space to work on the Canvas. +5. **Top bar:** Contains the “high level” editing controls: switching between UI and Preview mode, undoing and redoing the most recent change to the UI, and viewing the application’s state. +6. **Bottom bar:** Contains the “low level” editing controls, which toggle the Code and Log panels. -### Preview +### Defining your app’s user interface -![Framework Builder - Mode: Preview](/framework/images/builder-basics.preview.png) +Writer Framework Builder provides a selection of over 50 UI components that you can use to build your app’s user interface: -The _Preview_ mode shows the application exactly like the user will see it. It allocates the whole width of the viewport to the app. +![Visual catalog of all the components in the Core toolkit](/framework/images/builder-basics.components.png) -## Adding and moving components -You can create new components in your app by dragging and dropping items from the Toolkit. Some components, like Sections, can act as parents, while others, such as Text, cannot. Additionally, certain components have placement restrictions—for instance, a Column must be added to a Column Container, and a Sidebar can only be added to a Page. +You define your app’s user interface by dragging components from the Core toolkit and dropping them either onto the Canvas or into the Component tree (remember, the Canvas and Component tree provide different ways to look at your app’s user interface). If you simply drag and drop a UI component onto an empty spot on the Canvas, it will be placed at the “end,” below all other UI components in the user interface. To place a UI component at a specific location, drag them over the desired location or parent component until you see the insertion lines. -By default, components are positioned at the end, but if you need to place them specifically, simply drag them over the desired parent until you see the insertion lines. You can also reorganize existing components by moving them between parents or within the same parent. For more flexibility, the Component Tree can serve both as a source or a destination for your drag and drop actions. +![Writer Framework Builder with diagram showing how to drag and drop components from Core toolkit](/framework/images/builder-basics.drag-drop-components.png) -## Selecting a component -Select a component by clicking on it. If you click on a component that's already selected, the click will be treated as an interaction with the app. Two things will happen when a component is selected: +It can sometimes be difficult to find the component you’re looking for, so the Core toolkit has a search field. You can use it to find the narrow down the list of components or find a specific one. - - - The _Component Settings_ panel will open on the right. Depending on available screen real estate, the panel may open on top of the app or next to it. - - - A set of component-specific actions, _Component Shortcuts_, will be displayed on top of the component. - - +![Close-up of Core toolkit with instructions for using its Search text field](/framework/images/builder-basics.component-search.png) -## Component settings +Some UI components, such as Section, can act as “parents,” which are UI components that can contain other UI components. Others, such as Text, cannot. Additionally, certain components have placement restrictions — for instance, a Column must be added to a Column Container, and a Sidebar can only be added to a Page. -Settings are divided into the following sections. Changes to settings can be undone and redone using the buttons on the top bar. +![Writer Framework builder, showing parent-child relationship between a Section and the Text Input and Button components it contains](/framework/images/builder-basics.parent-components.png) -![Framework Builder - Component settings](/framework/images/builder-basics.component-settings.png) +The Component tree provides a hierarchical view of your app’s user interface. It shows the top-down layout of UI components in your app, as well as the parent-child relationships between components, making it easier to understand your app’s structure and ensure that components are correctly nested. + +You will find the Component tree useful when trying to reorganize the order of components in the UI, especially those located inside a parent UI component. For more flexibility and finer control, you can use the Component tree as a source or a destination for your drag and drop actions. + +### Discovering components + +The Builder is designed to allow easy discoverability of components. Rather than scouring specifications every time you need to use a component, you can rely on the visual editor to guide you. + +1. **Short description:** You can hover on the component type to get a tooltip with a short description. +2. **Available properties and events:** Looking at _Settings_ will allow you to see which of its properties are configurable. +3. **Built-in docs:** Components have short docs built into them. You can expand it by clicking the help icon in Settings. +4. **Event handler stub code:** Different events need to be handled differently. The built-in stub handlers, which can be found next to each event, can help you get started when writing event handlers. + +### Selecting and editing components + +To move or edit a component in your UI, you need to select it first. You can do this by clicking on the component either in the Canvas or in the Component tree. The selected component will be highlighted in both the Canvas and Component tree. + +![Writer Framework Builder, with instructions to select a component by clicking on it either on the Canvas or in the Component tree](/framework/images/builder-basics.select-components.png) + +Selecting a UI component will allow you to view and edit its settings in the Component settings panel on the right side of the page. + +![Writer Framework Builder, with Component settings panel on display, and instructions to hide the panel](/framework/images/builder-basics.component-settings.png) + +To hide the Component settings panel to get a better view of the Canvas, click on **»** in the button bar. To show the panel, click on **⚙** in the button bar. + +![Writer Framework Builder, with Component settings panel hidden, and instructions to show the panel](/framework/images/builder-basics.component-settings-show.png) + +The settings in the Component settings panel are divided into the following sections: - - Divided into _General_ and _Style_ categories. Values can include: + + ![Component settings panel, with basic settings highlighted](/framework/images/builder-basics.basic-settings.png) + + These settings are divided into two categores: + + 1. **General**, which specifies the component’s content + 2. **Style**, which defines the component’s appearance. + + Values for these settings can include: 1. Literals, e.g. `monkey` 2. References to application state using the template syntax `@{}`, e.g. `@{my_favourite_animal}`. 3. A combination of both, e.g. `My favourite animal is @{my_favourite_animal}`. 4. Nested states can be accessed with `.` (dot), e.g. `@{building.height}`. 5. Nested elements can be dynamically accessed with `[]`, e.g. `@{building[dynamic_prop]}` will be equivalent to `@{building.height}` when `dynamic_prop` equals `height`. - Properties are of different types, such as _Text_, _Color_ and _Number_. All property values are stored as text values, then casted when being evaluated. + + The values for these settings can be of different types, such as _Text_, _Color_ and _Number_. The values are stored as text and are cast to the correct type when evaluated. - ![Framework Builder - Binding](/framework/images/builder-basics.binding.png) - Input components can be bound, in a two-way fashion, to a state element. - For example, a _Slider Input_ component can be bound to `my_var`. If the value of the slider changes, so does the value of `my_var`. Similarly, if the value of `my_var` changes, the slider is moved automatically to reflect the change. + ![Component settings panel, with Binding settings highlighted](/framework/images/builder-basics.binding.png) + + Only input components have a **Binding** section, whose settings are used to bind the component to a variable in the application’s state. The binding is two-way; if the user changes the component’s value, the state variable will change to match, and any change the code makes to the bound state variable will also change the component’s value. + + For example, a _Slider Input_ component can be bound to a state variable `my_var`. If the value of the slider changes, so does the value of `my_var`. Similarly, if the value of `my_var` changes, the slider is moved automatically to reflect the change. + To bind an input component, specify the state element. For example, `my_var` or `building.height`. Note that this field should not contain the template syntax, e.g. `my_var` and not `@{my_var}`. - The events generated by this component, with the option of setting event handlers for those. Event handlers are explained in more detail in a separate section of this guide. + ![Component settings panel, with Events settings highlighted](/framework/images/builder-basics.events.png) + + The **Events** section lists all the events generated by the selected component, with the option of setting event handlers for them. For example, one of the events that the Text Input component generates is the `wf-change` event, which occurs whenever the user changes the contents of the component. + + For more about event handlers, consult the [_Event handlers_](/framework/event-handlers) section of the Writer Framework documentation. - Whether the component should be displayed. There are three visibility options: - 1. Yes. The component is displayed. - 2. No. The component isn't displayed. Note that hidden components are still part of the HTML code but aren't shown. - 3. Custom. Whether the component is displayed or not depends on the value of a state or context element. For example, if set to `my_var`, visibility will depend on the value of the `my_var` state element. Note that this field, similarly to Binding, should only contain the state element, e.g. `my_var` and not `@{my_var}`. + ![Component settings panel, with Visibility settings highlighted](/framework/images/builder-basics.visibility.png) + + The **Visibility** settings control Whether the component should be displayed. There are three visibility options: + + 1. **Yes**: The component is displayed. + 2. **No**: The component is _not_ displayed. Note that hidden components are still part of the HTML code but aren't shown. + 3. **Custom**: The component’s visibility depends on the value of a given state or context element. For example, if set to `my_var`, visibility will depend on the value of the `my_var` state element. Note that this field, similarly to Binding, should only contain the state element, e.g. `my_var` and not `@{my_var}`. -## Component shortcuts +### Component shortcuts -Perform a variety of operations on existing components. Options will be grayed out when they're not applicable to the relevant component. Most shortcuts can be triggered using the keyboard; hover on them to show the appropriate combination. +The Component shortcuts bar contains a set of options to perform various operations that are often performed on components. -![Framework Builder - Component shortcuts](/framework/images/builder-basics.component-shortcuts.png) +![Component settings panel, with Component shortcuts bar highlighted](/framework/images/builder-basics.component-shortcuts.png) - - - Adds a child of a specified type to this component. - - - Decrements the position index of the component, used to sort children within the parent container. - - - Increments the position index of the component. - - - Cuts the component and places it into Builder’s internal clipboard. - - - Copies the component and places it into the internal clipboard. - - - Pastes the content of the internal clipboard using the selected component as a parent. - - - Selects the parent of the selected component. - - - Deletes this component. - - +Options will be grayed out when they're not applicable to the relevant component. Most shortcuts can also be activated using the keyboard; hover the cursor over a shortcut to see its keyboard shortcut. +The shortcuts are: -Just like with changes to settings, these operations can be undone and redone. +![The Component shortcuts bar, with labels](/framework/images/builder-basics.component-shortcuts-guide.png) -## Discovering components +- **Add**: Adds a child of a specified type to the selected component. +- **Move up**: Decrements the position index of the selected component, used to sort children within the parent container. +- **Move down**: Increments the position index of the selected component. +- **Cut**: Cuts the selected component and places it in the clipboard. +- **Copy**: Copies the selected component to the clipboard. +- **Paste**: Pastes the content of the internal clipboard using the selected component as a parent. +- **Go to parent**: Selects the parent of the selected component. +- **Delete**: Deletes the selected component. + +### The Code editor and the Log + +Writer Framework Builder provides a built-in Code editor for the `main.py` file, which defines the behavior of the app. You can toggle the Code panel by clicking the **Code** control near the lower left corner of the page: + +![Writer Framework Builder, with the Code editor displayed](/framework/images/builder-basics.code.png) + +Any changes made to the code do not take effect until you click the **Save and run** button, located at the upper right corner of the code editor. + +The log is a useful debugging tool that displays any messages sent to standard output via the `print()` function, as well as any error messages. You can toggle the Log panel by clicking the **Log** control near the lower right corner of the page: + +![Writer Framework Builder, with the Log editor displayed](/framework/images/builder-basics.log.png) + +Note that both the Code editor and Log panes can be displayed at the same time: + +![Writer Framework Builder, with the Code editor and Log displayed at the same time](/framework/images/builder-basics.code-and-log.png) + + +## Testing your app in Preview mode + +In Preview mode, the overlays that let you build the user interface — the Core toolkit, Component tree, and Component settings — are invisible and unavailable. You see the app _almost_ as your users would see it; the Top bar and Bottom bar, which your users would not see, are still available to you in Preview mode. + +![Writer Framework Builder in preview mode](/framework/images/builder-basics.preview.png) + +You can still use the Code editor and Log in Preview mode: + +![Writer Framework Builder in Preview mode, with the Code editor and Log displayed at the same time](/framework/images/builder-basics.preview-code-and-log.png) -The Builder is designed to allow easy discoverability of components. Rather than scouring specifications every time you need to use a component, you can rely on the visual editor to guide you. -1. **Short description:** You can hover on the component type to get a tooltip with a short description. -2. **Available properties and events:** Looking at _Settings_ will allow you to see which of its properties are configurable. -3. **Built-in docs:** Components have short docs built into them. You can expand it by clicking the help icon in Settings. -4. **Event handler stub code:** Different events need to be handled differently. The built-in stub handlers, which can be found next to each event, can help you get started when writing event handlers. diff --git a/docs/framework/dataframe.mdx b/docs/framework/dataframe.mdx index 6a6ddc483..2cce63cb8 100644 --- a/docs/framework/dataframe.mdx +++ b/docs/framework/dataframe.mdx @@ -2,97 +2,253 @@ title: "Dataframe" --- -**writer framework places the dataframe at the core of the application**. This is a great way for modeling a complex and massive data system. -it offers components as `dataframe` to manipulate dataframes. These components allow you to visualize and interact with dataframes. +## DataFrames -| compatibility | dataframe | -|--------------------|---------------------------------------| -| `pandas.DataFrame` | x | -| `polar.DataFrame` | x | -| `list of records` | x (with `EditableDataFrame`) | +If your application needs to present data as a table, it should use a **DataFrame**. DataFrames provide a simple way to present data in a grid format, require only a couple of lines of code to set up, and provide an interface that users expect from modern data applications. -## Use a dataframe +![DataFrame showing a table of popular ice cream flavors](/framework/images/dataframe.png) -**a dataframe is simply added to the state**. A component like `dataframe` will be able to display it. +DataFrames built-in features that users expect, such as headers that can be clicked to change the sort order and resizable columns... -```python -import pandas -import writer as wf +![DataFrame with arrows pointing to the re-sort and resizing features](/framework/images/dataframe_resort_resize.png) -wf.init_state({ - 'mydf': pandas.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]}) -}) -``` +...and with the simple change of a parameter, you can enable features such as the Search field, which lets the user find the data they’re looking for (or filter out unwanted data), and the Download button, which downloads the data currently being displayed as a .csv file: + +![DataFrame with arrows pointing to the Search field and Download button](/framework/images/dataframe_search_download.png) + +You can find the full list of DataFrame properties and fields on the [_DataFrame_ component page](/components/dataframe). + +### “DataFrame” has multiple meanings + +**Writer Framework has two objects with the name _DataFrame_:** + +1. **UI DataFrame:** In Writer Framework's UI, "DataFrame" refers to a **_user interface component_** that displays data in rows and columns in a way similar to a spreadsheet or SQL table. +2. **Code Dataframe:** In code that you write for a Writer Framework application, `DataFrame` refers to a **_data structure_** that stores data in rows and columns in a way similar to a spreadsheet or SQL table. + +To present a data table in Writer Framework, you create a `DataFrame` data structure in your code and then bind it to a UI Dataframe. + + +## Displaying a static DataFrame + +A static DataFrame is one whose content does not change. The user can change its sort order, but the data within the DataFrame remains constant. + + + + Writer Framework supports both [pandas](https://pandas.pydata.org/) and [Polars](https://pola.rs/) `DataFrame` data structures. Create a `DataFrame`, assign its value to a variable, then assign make that variable a value in the `state` dictionary: + + + ```python pandas + import writer as wf + import pandas as pd + + data = [ + {"rank": 1, "flavor": "Vanilla", "favorite": 0.11}, + {"rank": 2, "flavor": "Chocolate", "favorite": 0.1}, + {"rank": 3, "flavor": "Cookies and cream", "favorite": 0.07}, + {"rank": 4, "flavor": "Strawberry", "favorite": 0.06}, + {"rank": 5, "flavor": "Chocolate chip", "favorite": 0.02}, + ] + df = pd.DataFrame(data) + + wf.init_state({ + "mydf": df + }) + ``` + + ```python Polars + import writer as wf + import polars as pl + + data = [ + {"rank": 1, "flavor": "Vanilla", "favorite": 0.11}, + {"rank": 2, "flavor": "Chocolate", "favorite": 0.1}, + {"rank": 3, "flavor": "Cookies and cream", "favorite": 0.07}, + {"rank": 4, "flavor": "Strawberry", "favorite": 0.06}, + {"rank": 5, "flavor": "Chocolate chip", "favorite": 0.02}, + ] + df = pl.DataFrame(data) + + wf.init_state({ + "mydf": df + }) + ``` + + + The call to `wf.init_state()` adds the `DataFrame` to the application's `state` variable as the value of the `mydf` key. + + + Add a DataFrame UI component to the user interface, then set its **Data** property to `@{`_dataframe_key_`}`, where _dataframe_key_ is the `state` variable key whose value refers to the `DataFrame` data structure. + + In the case of this example, `mydf` is the `state` variable key referring to the `DataFrame`, so set the **Data** property to `@{mydf}`. + + ![DataFrame for static table example with properties panel open](/framework/images/dataframe_static_table_1.png) + + + + +## Displaying an editable DataFrame + +A editable DataFrame is one whose content can change. Like static DataFrames, editable DataFrames use the **DataFrame** UI component. Unlike static tables, the DataFrame UI component is bound to an instance of `EditableDataFrame`, a class provided by the Writer library. Changes to a `EditableDataFrame` object will be immediately reflected in the DataFrame UI component that it is bound to. + + + + An `EditableDataFrame` object can be instantiated from any of the following: + + 1. A pandas `DataFrame` + 2. A Polars `DataFrame` + 3. A list of dictionaries + + + ```python pandas + import writer as wf + import pandas as pd -## Prepare a dataframe for editing + data = [ + {"rank": 1, "flavor": "Vanilla", "favorite": 0.11}, + {"rank": 2, "flavor": "Chocolate", "favorite": 0.1}, + {"rank": 3, "flavor": "Cookies and cream", "favorite": 0.07}, + {"rank": 4, "flavor": "Strawberry", "favorite": 0.06}, + {"rank": 5, "flavor": "Chocolate chip", "favorite": 0.02}, + ] + df = pd.DataFrame(data) -**writer provides `EditableDataFrame` as a helper to facilitate manipulation**. it makes it easier to write event handlers such as adding a line, -deleting it or modifying a value, etc... + wf.init_state({ + "mydf": wf.EditableDataFrame(df) + }) + ``` + + ```python Polars + import writer as wf + import polars as pl + + data = [ + {"rank": 1, "flavor": "Vanilla", "favorite": 0.11}, + {"rank": 2, "flavor": "Chocolate", "favorite": 0.1}, + {"rank": 3, "flavor": "Cookies and cream", "favorite": 0.07}, + {"rank": 4, "flavor": "Strawberry", "favorite": 0.06}, + {"rank": 5, "flavor": "Chocolate chip", "favorite": 0.02}, + ] + df = pl.DataFrame(data) + + wf.init_state({ + "mydf": wf.EditableDataFrame(df) + }) + ``` + + ```python List of dictionaries + import writer as wf + + data = [ + {"rank": 1, "flavor": "Vanilla", "favorite": 0.11}, + {"rank": 2, "flavor": "Chocolate", "favorite": 0.1}, + {"rank": 3, "flavor": "Cookies and cream", "favorite": 0.07}, + {"rank": 4, "flavor": "Strawberry", "favorite": 0.06}, + {"rank": 5, "flavor": "Chocolate chip", "favorite": 0.02}, + ] + + wf.init_state({ + "mydf": wf.EditableDataFrame(data) + }) + ``` + + + The call to `wf.init_state()` adds the `DataFrame` to the application's `state` variable as the value of the `mydf` key. + + + Add a **DataFrame** component to the user interface, then set its **Data** property to `@{`_dataframe_key_`}`, where _dataframe_key_ is the `state` variable key whose value refers to the `DataFrame` data structure. + + In the case of this example, `mydf` is the `state` variable key referring to the `DataFrame`, so set the **Data** property to `@{mydf}`. + + ![DataFrame for dynamic table example with properties panel open](/framework/images/dataframe_dynamic_table_1.png) + + + +## Updating an editable DataFrame + +Editable DataFrames are updated by updating the `EditableDataFrame` object they are bound to, which is done using `EditableDataFrame`'s methods. + +### `record_add`: Add a new row + +`record_add()` adds a new row to an `EditableDataFrame`. It takes a dictionary with the following structure... ```python -import pandas -import writer as wf +{"record": new_row} +``` -df = pandas.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]}) +...where `new_row` is a dictionary containing the data for the row to be added. -wf.init_state({ - 'mydf': wf.EditableDataFrame(df) -}) +In the code example above, you would add a new row to the DataFrame with the following code: + +```python +state["mydf"].record_add({"record": {"rank": 6, "flavor": "Birthday cake", "favorite": 0.01}}) ``` -### Handle events from a dataframe editor +### `record`: Read the contents of a row -**The dataframe component emits events when an action is performed**. You must subscribe to events to integrate changes to the state of the application. +`record()` returns a row in an `EditableDataFrame`. It takes an integer specifying the index of the row. + +In the code example above, you would retrieve the record at row 1 with the following code: ```python -import pandas -import writer as wf +record = state["mydf"].record(1) +``` -df = pandas.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]}) -wf.init_state({ - 'mydf': wf.EditableDataFrame(df) -}) +### `record_update`: Change an existing row -# Subscribe this event handler to the `wf-dataframe-add` event -def on_record_add(state, payload): - payload['record']['sales'] = 0 # default value inside the dataframe - state['mydf'].record_add(payload) +`record_update()` replaces an existing row in an `EditableDataFrame` with a new one. It takes a dictionary with the following structure... +```python +{ + "record_index": index, + "record": row_to_update +} +``` -# Subscribe this event handler to the `wf-dataframe-update` event -def on_record_change(state, payload): - state['mydf'].record_update(payload) +...where `index` is an integer specifying which row should be updated and `row_to_update` is a dictionary containing the updated row data. +In the code example above, you would update the row at index 0 with the following code: -# Subscribe this event handler to the `wf-dataframe-action` event -def on_record_action(state, payload): - """ - This event corresponds to a quick action in the drop-down menu to the left of the dataframe. - """ - record_index = payload['record_index'] - if payload['action'] == 'remove': - state['mydf'].record_remove(payload) - if payload['action'] == 'open': - state['record'] = state['df'].record(record_index) # dict representation of record +```python +state["mydf"].record_update({ + "record_index": 0, + "record": {"rank": 6, "flavor": "Bubble gum", "favorite": 0.08} +}) ``` -### Datastructures supported by `EditableDataFrame` +### `record_remove`: Delete an existing row -`EditableDataFrame` can be used with a panda dataframe, a polar dataframe and list of records. +`record_remove()` removes an existing row from an `EditableDataFrame`. It takes a dictionary with the following structure... ```python -import pandas -import polars +{"record_index": index} +``` -import writer as wf +...where `index` is an integer specifying which row should be deleted. -panda_df = pandas.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]}) -polars_df = polars.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]}) -list_of_records = [{'a': 1, 'b': 4}, {'a': 2, 'b': 5}, {'a': 3, 'b': 6}] +In the code example above, you would delete the row at index 2 with the following code: -wf.init_state({ - 'mypandas': wf.EditableDataFrame(panda_df), - 'mypolars': wf.EditableDataFrame(polars_df), - 'mylistofrecords': wf.EditableDataFrame(list_of_records) -}) +```python +state["mydf"].record_remove({"record_index": 2}) ``` + + +## Enabling additional features + +The DataFrame component has these “always-on” features: + +1. **Sorting:** Clicking a column header sorts the entire DataFrame based on that column's values. The first click sorts that DataFrame in ascending order, a second click changes it to descending order, and a third click restores the DataFrame to its original sort order. +2. **Column resizing:** Click and drag the dividing line on the right edge of a column header to adjust its width. + +DataFrames have other features that you need to activate, which are listed below. + +### Search field + +To enable the Search field, select the DataFrame, open the Component settings panel, and set **Enable search** to **yes**. + +![Enabling the Search field in a DataFrame](/framework/images/dataframe_enable_search.png) + +### Download button + +To enable the Download button, select the DataFrame, open the Component settings panel, and set **Enable download** to **yes**. + +![Enabling the Download button in a DataFrame](/framework/images/dataframe_enable_download.png) diff --git a/docs/framework/frontend-scripts.mdx b/docs/framework/frontend-scripts.mdx index b1fa0fa11..82e18bde8 100644 --- a/docs/framework/frontend-scripts.mdx +++ b/docs/framework/frontend-scripts.mdx @@ -88,7 +88,7 @@ initial_state.import_script("lodash", "https://cdnjs.cloudflare.com/ajax/libs/lo ## Frontend core -Effectively using Framework's core can be challenging and will likely entail reading its [source code](https://github.com/writer/writer-framework/blob/master/ui/src/core/index.ts). Furthermore, it's considered an internal capability rather than a public API, so it may unexpectedly change between releases. +Effectively using Framework's core can be challenging and will likely entail reading its [source code](https://github.com/writer/writer-framework/blob/master/src/ui/src/core/index.ts). Furthermore, it's considered an internal capability rather than a public API, so it may unexpectedly change between releases. You can access Framework's front-end core via `globalThis.core`, unlocking all sorts of functionality. Notably, you can use `getUserState()` to get values from state. diff --git a/docs/framework/images/builder-basics.basic-settings.png b/docs/framework/images/builder-basics.basic-settings.png new file mode 100644 index 000000000..8c39b141b Binary files /dev/null and b/docs/framework/images/builder-basics.basic-settings.png differ diff --git a/docs/framework/images/builder-basics.binding.png b/docs/framework/images/builder-basics.binding.png index 79fd15e5e..22bbfabae 100644 Binary files a/docs/framework/images/builder-basics.binding.png and b/docs/framework/images/builder-basics.binding.png differ diff --git a/docs/framework/images/builder-basics.code-and-log.png b/docs/framework/images/builder-basics.code-and-log.png new file mode 100644 index 000000000..5c576fbd9 Binary files /dev/null and b/docs/framework/images/builder-basics.code-and-log.png differ diff --git a/docs/framework/images/builder-basics.code.png b/docs/framework/images/builder-basics.code.png index 01071e7a6..add6aa829 100644 Binary files a/docs/framework/images/builder-basics.code.png and b/docs/framework/images/builder-basics.code.png differ diff --git a/docs/framework/images/builder-basics.component-search.png b/docs/framework/images/builder-basics.component-search.png new file mode 100644 index 000000000..2f3e91c44 Binary files /dev/null and b/docs/framework/images/builder-basics.component-search.png differ diff --git a/docs/framework/images/builder-basics.component-settings-show.png b/docs/framework/images/builder-basics.component-settings-show.png new file mode 100644 index 000000000..822e5a85c Binary files /dev/null and b/docs/framework/images/builder-basics.component-settings-show.png differ diff --git a/docs/framework/images/builder-basics.component-settings.png b/docs/framework/images/builder-basics.component-settings.png index dd5416d73..b0d32e524 100644 Binary files a/docs/framework/images/builder-basics.component-settings.png and b/docs/framework/images/builder-basics.component-settings.png differ diff --git a/docs/framework/images/builder-basics.component-shortcuts-guide.png b/docs/framework/images/builder-basics.component-shortcuts-guide.png new file mode 100644 index 000000000..9a35b521c Binary files /dev/null and b/docs/framework/images/builder-basics.component-shortcuts-guide.png differ diff --git a/docs/framework/images/builder-basics.component-shortcuts.png b/docs/framework/images/builder-basics.component-shortcuts.png index 5ff4e51fb..dafee3712 100644 Binary files a/docs/framework/images/builder-basics.component-shortcuts.png and b/docs/framework/images/builder-basics.component-shortcuts.png differ diff --git a/docs/framework/images/builder-basics.components.png b/docs/framework/images/builder-basics.components.png new file mode 100644 index 000000000..6e6b93cdd Binary files /dev/null and b/docs/framework/images/builder-basics.components.png differ diff --git a/docs/framework/images/builder-basics.drag-drop-components.png b/docs/framework/images/builder-basics.drag-drop-components.png new file mode 100644 index 000000000..e16cb695c Binary files /dev/null and b/docs/framework/images/builder-basics.drag-drop-components.png differ diff --git a/docs/framework/images/builder-basics.events.png b/docs/framework/images/builder-basics.events.png new file mode 100644 index 000000000..2b4e8b19f Binary files /dev/null and b/docs/framework/images/builder-basics.events.png differ diff --git a/docs/framework/images/builder-basics.log.png b/docs/framework/images/builder-basics.log.png new file mode 100644 index 000000000..e94623004 Binary files /dev/null and b/docs/framework/images/builder-basics.log.png differ diff --git a/docs/framework/images/builder-basics.parent-components.png b/docs/framework/images/builder-basics.parent-components.png new file mode 100644 index 000000000..80bcdbcc3 Binary files /dev/null and b/docs/framework/images/builder-basics.parent-components.png differ diff --git a/docs/framework/images/builder-basics.preview-code-and-log.png b/docs/framework/images/builder-basics.preview-code-and-log.png new file mode 100644 index 000000000..aa724267b Binary files /dev/null and b/docs/framework/images/builder-basics.preview-code-and-log.png differ diff --git a/docs/framework/images/builder-basics.preview-mode.png b/docs/framework/images/builder-basics.preview-mode.png new file mode 100644 index 000000000..c376b2efa Binary files /dev/null and b/docs/framework/images/builder-basics.preview-mode.png differ diff --git a/docs/framework/images/builder-basics.preview.png b/docs/framework/images/builder-basics.preview.png index 9bedc03db..e735f2391 100644 Binary files a/docs/framework/images/builder-basics.preview.png and b/docs/framework/images/builder-basics.preview.png differ diff --git a/docs/framework/images/builder-basics.select-components.png b/docs/framework/images/builder-basics.select-components.png new file mode 100644 index 000000000..c9e32a04a Binary files /dev/null and b/docs/framework/images/builder-basics.select-components.png differ diff --git a/docs/framework/images/builder-basics.tree-search.png b/docs/framework/images/builder-basics.tree-search.png new file mode 100644 index 000000000..fd21e1bef Binary files /dev/null and b/docs/framework/images/builder-basics.tree-search.png differ diff --git a/docs/framework/images/builder-basics.ui-mode-with-labels.png b/docs/framework/images/builder-basics.ui-mode-with-labels.png new file mode 100644 index 000000000..5a27f63ef Binary files /dev/null and b/docs/framework/images/builder-basics.ui-mode-with-labels.png differ diff --git a/docs/framework/images/builder-basics.ui-mode.png b/docs/framework/images/builder-basics.ui-mode.png new file mode 100644 index 000000000..292a7233e Binary files /dev/null and b/docs/framework/images/builder-basics.ui-mode.png differ diff --git a/docs/framework/images/builder-basics.ui-preview-selector.png b/docs/framework/images/builder-basics.ui-preview-selector.png new file mode 100644 index 000000000..38e37cdde Binary files /dev/null and b/docs/framework/images/builder-basics.ui-preview-selector.png differ diff --git a/docs/framework/images/builder-basics.visibility.png b/docs/framework/images/builder-basics.visibility.png new file mode 100644 index 000000000..30b641b76 Binary files /dev/null and b/docs/framework/images/builder-basics.visibility.png differ diff --git a/docs/framework/images/dataframe.png b/docs/framework/images/dataframe.png new file mode 100644 index 000000000..c1c132d80 Binary files /dev/null and b/docs/framework/images/dataframe.png differ diff --git a/docs/framework/images/dataframe_dynamic_table_1.png b/docs/framework/images/dataframe_dynamic_table_1.png new file mode 100644 index 000000000..00cf2dae1 Binary files /dev/null and b/docs/framework/images/dataframe_dynamic_table_1.png differ diff --git a/docs/framework/images/dataframe_enable_download.png b/docs/framework/images/dataframe_enable_download.png new file mode 100644 index 000000000..58ba94460 Binary files /dev/null and b/docs/framework/images/dataframe_enable_download.png differ diff --git a/docs/framework/images/dataframe_enable_search.png b/docs/framework/images/dataframe_enable_search.png new file mode 100644 index 000000000..93b9752d5 Binary files /dev/null and b/docs/framework/images/dataframe_enable_search.png differ diff --git a/docs/framework/images/dataframe_resort_resize.png b/docs/framework/images/dataframe_resort_resize.png new file mode 100644 index 000000000..fd7532009 Binary files /dev/null and b/docs/framework/images/dataframe_resort_resize.png differ diff --git a/docs/framework/images/dataframe_search_download.png b/docs/framework/images/dataframe_search_download.png new file mode 100644 index 000000000..4c3b278db Binary files /dev/null and b/docs/framework/images/dataframe_search_download.png differ diff --git a/docs/framework/images/dataframe_static_table_1.png b/docs/framework/images/dataframe_static_table_1.png new file mode 100644 index 000000000..70ad1517d Binary files /dev/null and b/docs/framework/images/dataframe_static_table_1.png differ diff --git a/package-lock.json b/package-lock.json index 649fd5198..e81e7f0d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5157,6 +5157,13 @@ "@octokit/openapi-types": "^18.0.0" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "dev": true, @@ -5178,18 +5185,19 @@ } }, "node_modules/@playwright/test": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz", - "integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright": "1.42.1" + "playwright": "1.49.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@radix-ui/react-compose-refs": { @@ -5227,6 +5235,20 @@ } } }, + "node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.14.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz", @@ -7972,6 +7994,17 @@ "version": "3.4.21", "license": "MIT" }, + "node_modules/@vue/test-utils": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", + "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^2.0.0" + } + }, "node_modules/@yarnpkg/esbuild-plugin-pnp": { "version": "3.0.0-rc.15", "dev": true, @@ -9501,6 +9534,17 @@ "devOptional": true, "license": "MIT" }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "node_modules/consola": { "version": "3.2.3", "dev": true, @@ -9713,6 +9757,19 @@ "node": ">=4" } }, + "node_modules/cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rrweb-cssom": "^0.7.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/csstype": { "version": "3.1.3", "license": "MIT" @@ -9983,6 +10040,57 @@ "node": ">=12" } }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", + "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/de-indent": { "version": "1.0.2", "dev": true, @@ -10004,6 +10112,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true, + "license": "MIT" + }, "node_modules/decode-named-character-reference": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", @@ -10449,6 +10564,51 @@ "dev": true, "license": "MIT" }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ee-first": { "version": "1.1.1", "license": "MIT" @@ -11460,6 +11620,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.17.1", "dev": true, @@ -12809,6 +12975,19 @@ "dev": true, "license": "ISC" }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "dev": true, @@ -12854,6 +13033,30 @@ "node": ">=8.0.0" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/http2-wrapper": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", @@ -12991,6 +13194,13 @@ "version": "2.0.4", "license": "ISC" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/inline-style-parser": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", @@ -13487,6 +13697,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-promise": { "version": "2.2.2", "dev": true, @@ -15296,6 +15513,111 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-beautify": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-beautify/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/js-beautify/node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-stringify": { "version": "1.0.2", "dev": true, @@ -15427,6 +15749,118 @@ "signal-exit": "^3.0.2" } }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", + "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/jsesc": { "version": "2.5.2", "dev": true, @@ -17784,6 +18218,13 @@ "node": ">= 6" } }, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/nypm": { "version": "0.3.8", "dev": true, @@ -18295,6 +18736,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pad-left": { "version": "2.1.0", "license": "MIT", @@ -18429,7 +18877,9 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.10.2", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -18437,7 +18887,7 @@ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -18537,7 +18987,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { @@ -18626,33 +19078,35 @@ } }, "node_modules/playwright": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz", - "integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.42.1" + "playwright-core": "1.49.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz", - "integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", "dev": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/playwright/node_modules/fsevents": { @@ -18661,6 +19115,7 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -18695,9 +19150,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -18712,15 +19167,32 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-assign-layer": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/postcss-assign-layer/-/postcss-assign-layer-0.4.0.tgz", + "integrity": "sha512-ART9ENWnvEyW3yyfJe83nz5j/IsbQHBgSMsUNFmC/+6zy0uC9YTriHKV6TEjOOHXRzXS2yNPj0ksvYkEwwkv5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^4.2.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "postcss": "^8.3.0" + } + }, "node_modules/postcss-selector-parser": { "version": "6.0.15", "dev": true, @@ -18854,6 +19326,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, "node_modules/protocol-buffers-schema": { "version": "3.6.0", "license": "MIT" @@ -20143,6 +20622,13 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -20207,6 +20693,19 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.0", "license": "MIT", @@ -20560,9 +21059,10 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -20912,6 +21412,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/synckit": { "version": "0.8.8", "dev": true, @@ -21230,6 +21737,26 @@ "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "6.1.69", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.69.tgz", + "integrity": "sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.69" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.69", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.69.tgz", + "integrity": "sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA==", + "dev": true, + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "dev": true, @@ -21295,6 +21822,19 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tr46": { "version": "0.0.3", "license": "MIT" @@ -23118,6 +23658,29 @@ "he": "^1.2.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/w3c-xmlserializer/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/walker": { "version": "1.0.8", "dev": true, @@ -23171,6 +23734,42 @@ "dev": true, "license": "MIT" }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "license": "MIT", @@ -23412,7 +24011,9 @@ "link": true }, "node_modules/ws": { - "version": "8.16.0", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "license": "MIT", "engines": { @@ -23459,6 +24060,13 @@ "node": ">=4.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/xtend": { "version": "4.0.2", "dev": true, @@ -23548,6 +24156,7 @@ "@googlemaps/js-api-loader": "^1.16.6", "@monaco-editor/loader": "^1.3.3", "@tato30/vue-pdf": "^1.9.6", + "ajv": "^8.17.1", "arquero": "^5.2.0", "chroma-js": "^2.4.2", "fuse.js": "7.0.0", @@ -23578,10 +24187,14 @@ "@typescript-eslint/eslint-plugin": "7.18.0", "@vitejs/plugin-vue": "^5.0.4", "@vue/eslint-config-prettier": "^9.0.0", + "@vue/test-utils": "^2.4.6", "eslint": "^8.39.0", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-storybook": "0.8.0", "eslint-plugin-vue": "^9.28.0", + "jsdom": "^25.0.1", + "postcss": "^8.4.49", + "postcss-assign-layer": "^0.4.0", "prettier": "3.2.5", "storybook": "8.0.5", "vite": "^5.2.7", @@ -23622,6 +24235,28 @@ } } }, + "src/ui/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "src/ui/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "tests/e2e": { "name": "writer-e2e", "version": "1.0.0", @@ -23631,7 +24266,7 @@ "writer-ui": "*" }, "devDependencies": { - "@playwright/test": "1.42.1", + "@playwright/test": "^1.49.1", "nodemon": "3.1.0" } } diff --git a/poetry.lock b/poetry.lock index b4244e6b1..bebb90c3d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -106,13 +106,13 @@ tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "authlib" -version = "1.3.2" +version = "1.4.0" description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "Authlib-1.3.2-py2.py3-none-any.whl", hash = "sha256:ede026a95e9f5cdc2d4364a52103f5405e75aa156357e831ef2bfd0bc5094dfc"}, - {file = "authlib-1.3.2.tar.gz", hash = "sha256:4b16130117f9eb82aa6eec97f6dd4673c3f960ac0283ccdae2897ee4bc030ba2"}, + {file = "Authlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bb20b978c8b636222b549317c1815e1fe62234fc1c5efe8855d84aebf3a74e3"}, + {file = "authlib-1.4.0.tar.gz", hash = "sha256:1c1e6608b5ed3624aeeee136ca7f8c120d6f51f731aa152b153d54741840e1f2"}, ] [package.dependencies] @@ -210,127 +210,114 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.4.0" +version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.7" files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, + {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, + {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] @@ -360,6 +347,7 @@ files = [ {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"}, {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, @@ -370,6 +358,7 @@ files = [ {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"}, {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, @@ -534,13 +523,13 @@ files = [ [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] @@ -656,49 +645,55 @@ files = [ [[package]] name = "mypy" -version = "1.13.0" +version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, + {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, + {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, + {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, + {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, + {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, + {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, + {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, + {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, + {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, + {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, + {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, + {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, + {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, + {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, + {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, + {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, ] [package.dependencies] -mypy-extensions = ">=1.0.0" +mypy_extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -720,18 +715,21 @@ files = [ [[package]] name = "narwhals" -version = "1.18.4" +version = "1.20.1" description = "Extremely lightweight compatibility layer between dataframe libraries" optional = false python-versions = ">=3.8" files = [ - {file = "narwhals-1.18.4-py3-none-any.whl", hash = "sha256:c6bb6b6fba59caeab28a7d6ec1e79ab0040c75baef2e4152199ad1a9c266ef96"}, - {file = "narwhals-1.18.4.tar.gz", hash = "sha256:b1da4e2e4ab185824781760319ac1ec8ee2944a929795064c3a64ffff16b00c4"}, + {file = "narwhals-1.20.1-py3-none-any.whl", hash = "sha256:77fc10fed31534a4ecf0c5e1e091c91c454cb2fa73937f36be3fcb0c2dfdabc6"}, + {file = "narwhals-1.20.1.tar.gz", hash = "sha256:ffc6a44c1bc651531198c5f7fc38d349dff898ecfe51c1ef96aaaf429ec4dc19"}, ] [package.extras] -cudf = ["cudf (>=23.08.00)"] +cudf = ["cudf (>=24.10.0)"] dask = ["dask[dataframe] (>=2024.7)"] +dev = ["covdefaults", "duckdb", "hypothesis[numpy]", "pandas", "polars", "pre-commit", "pyarrow", "pyarrow-stubs", "pytest", "pytest-cov", "pytest-env", "pytest-randomly", "tqdm", "typing-extensions"] +docs = ["black", "duckdb", "jinja2", "markdown-exec[ansi]", "mkdocs", "mkdocs-autorefs", "mkdocs-material", "mkdocstrings[python]", "pandas", "polars (>=1.0.0)", "pyarrow"] +extra = ["dask[dataframe]", "modin", "pyspark", "scikit-learn"] modin = ["modin"] pandas = ["pandas (>=0.25.3)"] polars = ["polars (>=0.20.3)"] @@ -740,47 +738,120 @@ pyspark = ["pyspark (>=3.3.0)"] [[package]] name = "numpy" -version = "1.26.4" +version = "2.0.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"}, + {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"}, + {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"}, + {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"}, + {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"}, + {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"}, + {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"}, + {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"}, + {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"}, + {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"}, +] + +[[package]] +name = "numpy" +version = "2.2.1" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "numpy-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5edb4e4caf751c1518e6a26a83501fda79bff41cc59dac48d70e6d65d4ec4440"}, + {file = "numpy-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa3017c40d513ccac9621a2364f939d39e550c542eb2a894b4c8da92b38896ab"}, + {file = "numpy-2.2.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:61048b4a49b1c93fe13426e04e04fdf5a03f456616f6e98c7576144677598675"}, + {file = "numpy-2.2.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7671dc19c7019103ca44e8d94917eba8534c76133523ca8406822efdd19c9308"}, + {file = "numpy-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4250888bcb96617e00bfa28ac24850a83c9f3a16db471eca2ee1f1714df0f957"}, + {file = "numpy-2.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7746f235c47abc72b102d3bce9977714c2444bdfaea7888d241b4c4bb6a78bf"}, + {file = "numpy-2.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:059e6a747ae84fce488c3ee397cee7e5f905fd1bda5fb18c66bc41807ff119b2"}, + {file = "numpy-2.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f62aa6ee4eb43b024b0e5a01cf65a0bb078ef8c395e8713c6e8a12a697144528"}, + {file = "numpy-2.2.1-cp310-cp310-win32.whl", hash = "sha256:48fd472630715e1c1c89bf1feab55c29098cb403cc184b4859f9c86d4fcb6a95"}, + {file = "numpy-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:b541032178a718c165a49638d28272b771053f628382d5e9d1c93df23ff58dbf"}, + {file = "numpy-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40f9e544c1c56ba8f1cf7686a8c9b5bb249e665d40d626a23899ba6d5d9e1484"}, + {file = "numpy-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9b57eaa3b0cd8db52049ed0330747b0364e899e8a606a624813452b8203d5f7"}, + {file = "numpy-2.2.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bc8a37ad5b22c08e2dbd27df2b3ef7e5c0864235805b1e718a235bcb200cf1cb"}, + {file = "numpy-2.2.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9036d6365d13b6cbe8f27a0eaf73ddcc070cae584e5ff94bb45e3e9d729feab5"}, + {file = "numpy-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51faf345324db860b515d3f364eaa93d0e0551a88d6218a7d61286554d190d73"}, + {file = "numpy-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38efc1e56b73cc9b182fe55e56e63b044dd26a72128fd2fbd502f75555d92591"}, + {file = "numpy-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:31b89fa67a8042e96715c68e071a1200c4e172f93b0fbe01a14c0ff3ff820fc8"}, + {file = "numpy-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c86e2a209199ead7ee0af65e1d9992d1dce7e1f63c4b9a616500f93820658d0"}, + {file = "numpy-2.2.1-cp311-cp311-win32.whl", hash = "sha256:b34d87e8a3090ea626003f87f9392b3929a7bbf4104a05b6667348b6bd4bf1cd"}, + {file = "numpy-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:360137f8fb1b753c5cde3ac388597ad680eccbbbb3865ab65efea062c4a1fd16"}, + {file = "numpy-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:694f9e921a0c8f252980e85bce61ebbd07ed2b7d4fa72d0e4246f2f8aa6642ab"}, + {file = "numpy-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3683a8d166f2692664262fd4900f207791d005fb088d7fdb973cc8d663626faa"}, + {file = "numpy-2.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:780077d95eafc2ccc3ced969db22377b3864e5b9a0ea5eb347cc93b3ea900315"}, + {file = "numpy-2.2.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:55ba24ebe208344aa7a00e4482f65742969a039c2acfcb910bc6fcd776eb4355"}, + {file = "numpy-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b1d07b53b78bf84a96898c1bc139ad7f10fda7423f5fd158fd0f47ec5e01ac7"}, + {file = "numpy-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5062dc1a4e32a10dc2b8b13cedd58988261416e811c1dc4dbdea4f57eea61b0d"}, + {file = "numpy-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fce4f615f8ca31b2e61aa0eb5865a21e14f5629515c9151850aa936c02a1ee51"}, + {file = "numpy-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67d4cda6fa6ffa073b08c8372aa5fa767ceb10c9a0587c707505a6d426f4e046"}, + {file = "numpy-2.2.1-cp312-cp312-win32.whl", hash = "sha256:32cb94448be47c500d2c7a95f93e2f21a01f1fd05dd2beea1ccd049bb6001cd2"}, + {file = "numpy-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:ba5511d8f31c033a5fcbda22dd5c813630af98c70b2661f2d2c654ae3cdfcfc8"}, + {file = "numpy-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1d09e520217618e76396377c81fba6f290d5f926f50c35f3a5f72b01a0da780"}, + {file = "numpy-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ecc47cd7f6ea0336042be87d9e7da378e5c7e9b3c8ad0f7c966f714fc10d821"}, + {file = "numpy-2.2.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f419290bc8968a46c4933158c91a0012b7a99bb2e465d5ef5293879742f8797e"}, + {file = "numpy-2.2.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5b6c390bfaef8c45a260554888966618328d30e72173697e5cabe6b285fb2348"}, + {file = "numpy-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:526fc406ab991a340744aad7e25251dd47a6720a685fa3331e5c59fef5282a59"}, + {file = "numpy-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74e6fdeb9a265624ec3a3918430205dff1df7e95a230779746a6af78bc615af"}, + {file = "numpy-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:53c09385ff0b72ba79d8715683c1168c12e0b6e84fb0372e97553d1ea91efe51"}, + {file = "numpy-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3eac17d9ec51be534685ba877b6ab5edc3ab7ec95c8f163e5d7b39859524716"}, + {file = "numpy-2.2.1-cp313-cp313-win32.whl", hash = "sha256:9ad014faa93dbb52c80d8f4d3dcf855865c876c9660cb9bd7553843dd03a4b1e"}, + {file = "numpy-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:164a829b6aacf79ca47ba4814b130c4020b202522a93d7bff2202bfb33b61c60"}, + {file = "numpy-2.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4dfda918a13cc4f81e9118dea249e192ab167a0bb1966272d5503e39234d694e"}, + {file = "numpy-2.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:733585f9f4b62e9b3528dd1070ec4f52b8acf64215b60a845fa13ebd73cd0712"}, + {file = "numpy-2.2.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:89b16a18e7bba224ce5114db863e7029803c179979e1af6ad6a6b11f70545008"}, + {file = "numpy-2.2.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:676f4eebf6b2d430300f1f4f4c2461685f8269f94c89698d832cdf9277f30b84"}, + {file = "numpy-2.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f5cdf9f493b35f7e41e8368e7d7b4bbafaf9660cba53fb21d2cd174ec09631"}, + {file = "numpy-2.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1ad395cf254c4fbb5b2132fee391f361a6e8c1adbd28f2cd8e79308a615fe9d"}, + {file = "numpy-2.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:08ef779aed40dbc52729d6ffe7dd51df85796a702afbf68a4f4e41fafdc8bda5"}, + {file = "numpy-2.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:26c9c4382b19fcfbbed3238a14abf7ff223890ea1936b8890f058e7ba35e8d71"}, + {file = "numpy-2.2.1-cp313-cp313t-win32.whl", hash = "sha256:93cf4e045bae74c90ca833cba583c14b62cb4ba2cba0abd2b141ab52548247e2"}, + {file = "numpy-2.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bff7d8ec20f5f42607599f9994770fa65d76edca264a87b5e4ea5629bce12268"}, + {file = "numpy-2.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7ba9cc93a91d86365a5d270dee221fdc04fb68d7478e6bf6af650de78a8339e3"}, + {file = "numpy-2.2.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3d03883435a19794e41f147612a77a8f56d4e52822337844fff3d4040a142964"}, + {file = "numpy-2.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4511d9e6071452b944207c8ce46ad2f897307910b402ea5fa975da32e0102800"}, + {file = "numpy-2.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5c5cc0cbabe9452038ed984d05ac87910f89370b9242371bd9079cb4af61811e"}, + {file = "numpy-2.2.1.tar.gz", hash = "sha256:45681fd7128c8ad1c379f0ca0776a8b0c6583d2f69889ddac01559dfe4390918"}, ] [[package]] @@ -980,51 +1051,57 @@ wcwidth = "*" [[package]] name = "pyarrow" -version = "15.0.2" +version = "18.1.0" description = "Python library for Apache Arrow" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pyarrow-15.0.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:88b340f0a1d05b5ccc3d2d986279045655b1fe8e41aba6ca44ea28da0d1455d8"}, - {file = "pyarrow-15.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eaa8f96cecf32da508e6c7f69bb8401f03745c050c1dd42ec2596f2e98deecac"}, - {file = "pyarrow-15.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23c6753ed4f6adb8461e7c383e418391b8d8453c5d67e17f416c3a5d5709afbd"}, - {file = "pyarrow-15.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f639c059035011db8c0497e541a8a45d98a58dbe34dc8fadd0ef128f2cee46e5"}, - {file = "pyarrow-15.0.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:290e36a59a0993e9a5224ed2fb3e53375770f07379a0ea03ee2fce2e6d30b423"}, - {file = "pyarrow-15.0.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:06c2bb2a98bc792f040bef31ad3e9be6a63d0cb39189227c08a7d955db96816e"}, - {file = "pyarrow-15.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:f7a197f3670606a960ddc12adbe8075cea5f707ad7bf0dffa09637fdbb89f76c"}, - {file = "pyarrow-15.0.2-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:5f8bc839ea36b1f99984c78e06e7a06054693dc2af8920f6fb416b5bca9944e4"}, - {file = "pyarrow-15.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5e81dfb4e519baa6b4c80410421528c214427e77ca0ea9461eb4097c328fa33"}, - {file = "pyarrow-15.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a4f240852b302a7af4646c8bfe9950c4691a419847001178662a98915fd7ee7"}, - {file = "pyarrow-15.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e7d9cfb5a1e648e172428c7a42b744610956f3b70f524aa3a6c02a448ba853e"}, - {file = "pyarrow-15.0.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2d4f905209de70c0eb5b2de6763104d5a9a37430f137678edfb9a675bac9cd98"}, - {file = "pyarrow-15.0.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90adb99e8ce5f36fbecbbc422e7dcbcbed07d985eed6062e459e23f9e71fd197"}, - {file = "pyarrow-15.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:b116e7fd7889294cbd24eb90cd9bdd3850be3738d61297855a71ac3b8124ee38"}, - {file = "pyarrow-15.0.2-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:25335e6f1f07fdaa026a61c758ee7d19ce824a866b27bba744348fa73bb5a440"}, - {file = "pyarrow-15.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:90f19e976d9c3d8e73c80be84ddbe2f830b6304e4c576349d9360e335cd627fc"}, - {file = "pyarrow-15.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a22366249bf5fd40ddacc4f03cd3160f2d7c247692945afb1899bab8a140ddfb"}, - {file = "pyarrow-15.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2a335198f886b07e4b5ea16d08ee06557e07db54a8400cc0d03c7f6a22f785f"}, - {file = "pyarrow-15.0.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e6d459c0c22f0b9c810a3917a1de3ee704b021a5fb8b3bacf968eece6df098f"}, - {file = "pyarrow-15.0.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:033b7cad32198754d93465dcfb71d0ba7cb7cd5c9afd7052cab7214676eec38b"}, - {file = "pyarrow-15.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:29850d050379d6e8b5a693098f4de7fd6a2bea4365bfd073d7c57c57b95041ee"}, - {file = "pyarrow-15.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:7167107d7fb6dcadb375b4b691b7e316f4368f39f6f45405a05535d7ad5e5058"}, - {file = "pyarrow-15.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e85241b44cc3d365ef950432a1b3bd44ac54626f37b2e3a0cc89c20e45dfd8bf"}, - {file = "pyarrow-15.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:248723e4ed3255fcd73edcecc209744d58a9ca852e4cf3d2577811b6d4b59818"}, - {file = "pyarrow-15.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ff3bdfe6f1b81ca5b73b70a8d482d37a766433823e0c21e22d1d7dde76ca33f"}, - {file = "pyarrow-15.0.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:f3d77463dee7e9f284ef42d341689b459a63ff2e75cee2b9302058d0d98fe142"}, - {file = "pyarrow-15.0.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:8c1faf2482fb89766e79745670cbca04e7018497d85be9242d5350cba21357e1"}, - {file = "pyarrow-15.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:28f3016958a8e45a1069303a4a4f6a7d4910643fc08adb1e2e4a7ff056272ad3"}, - {file = "pyarrow-15.0.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:89722cb64286ab3d4daf168386f6968c126057b8c7ec3ef96302e81d8cdb8ae4"}, - {file = "pyarrow-15.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cd0ba387705044b3ac77b1b317165c0498299b08261d8122c96051024f953cd5"}, - {file = "pyarrow-15.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad2459bf1f22b6a5cdcc27ebfd99307d5526b62d217b984b9f5c974651398832"}, - {file = "pyarrow-15.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58922e4bfece8b02abf7159f1f53a8f4d9f8e08f2d988109126c17c3bb261f22"}, - {file = "pyarrow-15.0.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:adccc81d3dc0478ea0b498807b39a8d41628fa9210729b2f718b78cb997c7c91"}, - {file = "pyarrow-15.0.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:8bd2baa5fe531571847983f36a30ddbf65261ef23e496862ece83bdceb70420d"}, - {file = "pyarrow-15.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6669799a1d4ca9da9c7e06ef48368320f5856f36f9a4dd31a11839dda3f6cc8c"}, - {file = "pyarrow-15.0.2.tar.gz", hash = "sha256:9c9bc803cb3b7bfacc1e96ffbfd923601065d9d3f911179d81e72d99fd74a3d9"}, + {file = "pyarrow-18.1.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e21488d5cfd3d8b500b3238a6c4b075efabc18f0f6d80b29239737ebd69caa6c"}, + {file = "pyarrow-18.1.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b516dad76f258a702f7ca0250885fc93d1fa5ac13ad51258e39d402bd9e2e1e4"}, + {file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f443122c8e31f4c9199cb23dca29ab9427cef990f283f80fe15b8e124bcc49b"}, + {file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a03da7f2758645d17b7b4f83c8bffeae5bbb7f974523fe901f36288d2eab71"}, + {file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ba17845efe3aa358ec266cf9cc2800fa73038211fb27968bfa88acd09261a470"}, + {file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3c35813c11a059056a22a3bef520461310f2f7eea5c8a11ef9de7062a23f8d56"}, + {file = "pyarrow-18.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9736ba3c85129d72aefa21b4f3bd715bc4190fe4426715abfff90481e7d00812"}, + {file = "pyarrow-18.1.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eaeabf638408de2772ce3d7793b2668d4bb93807deed1725413b70e3156a7854"}, + {file = "pyarrow-18.1.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:3b2e2239339c538f3464308fd345113f886ad031ef8266c6f004d49769bb074c"}, + {file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f39a2e0ed32a0970e4e46c262753417a60c43a3246972cfc2d3eb85aedd01b21"}, + {file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31e9417ba9c42627574bdbfeada7217ad8a4cbbe45b9d6bdd4b62abbca4c6f6"}, + {file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:01c034b576ce0eef554f7c3d8c341714954be9b3f5d5bc7117006b85fcf302fe"}, + {file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f266a2c0fc31995a06ebd30bcfdb7f615d7278035ec5b1cd71c48d56daaf30b0"}, + {file = "pyarrow-18.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d4f13eee18433f99adefaeb7e01d83b59f73360c231d4782d9ddfaf1c3fbde0a"}, + {file = "pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d"}, + {file = "pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee"}, + {file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992"}, + {file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54"}, + {file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33"}, + {file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30"}, + {file = "pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99"}, + {file = "pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b"}, + {file = "pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2"}, + {file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191"}, + {file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa"}, + {file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c"}, + {file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c"}, + {file = "pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181"}, + {file = "pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc"}, + {file = "pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386"}, + {file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324"}, + {file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8"}, + {file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9"}, + {file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba"}, + {file = "pyarrow-18.1.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:0b331e477e40f07238adc7ba7469c36b908f07c89b95dd4bd3a0ec84a3d1e21e"}, + {file = "pyarrow-18.1.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:2c4dd0c9010a25ba03e198fe743b1cc03cd33c08190afff371749c52ccbbaf76"}, + {file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f97b31b4c4e21ff58c6f330235ff893cc81e23da081b1a4b1c982075e0ed4e9"}, + {file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a4813cb8ecf1809871fd2d64a8eff740a1bd3691bbe55f01a3cf6c5ec869754"}, + {file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:05a5636ec3eb5cc2a36c6edb534a38ef57b2ab127292a716d00eabb887835f1e"}, + {file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:73eeed32e724ea3568bb06161cad5fa7751e45bc2228e33dcb10c614044165c7"}, + {file = "pyarrow-18.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:a1880dd6772b685e803011a6b43a230c23b566859a6e0c9a276c1e0faf4f4052"}, + {file = "pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73"}, ] -[package.dependencies] -numpy = ">=1.16.6,<2" +[package.extras] +test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] [[package]] name = "pycparser" @@ -1039,18 +1116,18 @@ files = [ [[package]] name = "pydantic" -version = "2.10.3" +version = "2.10.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, - {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, + {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, + {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.27.1" +pydantic-core = "2.27.2" typing-extensions = ">=4.12.2" [package.extras] @@ -1059,111 +1136,111 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.27.1" +version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, - {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, - {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, - {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, - {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, - {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, - {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, - {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, - {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, - {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, - {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, - {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, - {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"}, - {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"}, - {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"}, - {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, - {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, - {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, - {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, ] [package.dependencies] @@ -1225,13 +1302,13 @@ six = ">=1.5" [[package]] name = "python-multipart" -version = "0.0.19" +version = "0.0.20" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" files = [ - {file = "python_multipart-0.0.19-py3-none-any.whl", hash = "sha256:f8d5b0b9c618575bf9df01c684ded1d94a338839bdd8223838afacfb4bb2082d"}, - {file = "python_multipart-0.0.19.tar.gz", hash = "sha256:905502ef39050557b7a6af411f454bc19526529ca46ae6831508438890ce12cc"}, + {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, + {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, ] [[package]] @@ -1568,13 +1645,13 @@ files = [ [[package]] name = "types-pytz" -version = "2024.2.0.20241003" +version = "2024.2.0.20241221" description = "Typing stubs for pytz" optional = false python-versions = ">=3.8" files = [ - {file = "types-pytz-2024.2.0.20241003.tar.gz", hash = "sha256:575dc38f385a922a212bac00a7d6d2e16e141132a3c955078f4a4fd13ed6cb44"}, - {file = "types_pytz-2024.2.0.20241003-py3-none-any.whl", hash = "sha256:3e22df1336c0c6ad1d29163c8fda82736909eb977281cb823c57f8bae07118b7"}, + {file = "types_pytz-2024.2.0.20241221-py3-none-any.whl", hash = "sha256:8fc03195329c43637ed4f593663df721fef919b60a969066e22606edf0b53ad5"}, + {file = "types_pytz-2024.2.0.20241221.tar.gz", hash = "sha256:06d7cde9613e9f7504766a0554a270c369434b50e00975b3a4a0f6eed0f2c1a9"}, ] [[package]] @@ -1615,13 +1692,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.3" +version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, + {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, + {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] @@ -1782,13 +1859,13 @@ files = [ [[package]] name = "writer-sdk" -version = "1.5.0" +version = "1.6.1" description = "The official Python library for the writer API" optional = false python-versions = ">=3.8" files = [ - {file = "writer_sdk-1.5.0-py3-none-any.whl", hash = "sha256:e144b7e648dd5c64d3619a7c46d83c446b689774758b983be4d6f49ccb5953b7"}, - {file = "writer_sdk-1.5.0.tar.gz", hash = "sha256:9fcabffb37cb0d20b23e80d028d7449b3e7c94b8cecc87a8d9285fe6f6c869ba"}, + {file = "writer_sdk-1.6.1-py3-none-any.whl", hash = "sha256:72b26c26441cc72140f4992ce2de211aa348ce2bc7cb25f83be40c5b824a455f"}, + {file = "writer_sdk-1.6.1.tar.gz", hash = "sha256:12dc5b01266a19bf4b17cb5e47310e4743c906c271693a6f0a370ea544256402"}, ] [package.dependencies] @@ -1797,7 +1874,7 @@ distro = ">=1.7.0,<2" httpx = ">=0.23.0,<1" pydantic = ">=1.9.0,<3" sniffio = "*" -typing-extensions = ">=4.7,<5" +typing-extensions = ">=4.10,<5" [extras] redis = [] @@ -1805,4 +1882,4 @@ redis = [] [metadata] lock-version = "2.0" python-versions = ">=3.9.2, <4.0" -content-hash = "eef09bbf43e87914f27d85100a012608014f55be3d4e89c5b70f4d0810579a10" +content-hash = "58347fd3fef100e54469d647f99286efc3510fe6b87391ccd566a24fa6d6b40e" diff --git a/pyproject.toml b/pyproject.toml index 1dac4d093..fd13fed05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "writer" -version = "0.8.3rc3" +version = "0.8.3rc4" description = "An open-source, Python framework for building feature-rich apps that are fully integrated with the Writer platform." authors = ["Writer, Inc."] readme = "README.md" @@ -37,8 +37,12 @@ gitignore-parser = "^0.1.11" jinja2 = "^3.1.4" pandas = ">= 2.2.0, < 3" plotly = "^5.24.1" -pyarrow = ">= 15.0.0, < 16.0.0" +pyarrow = ">= 15.0.0, < 19.0.0" pydantic = ">= 2.6.0, < 3" +numpy = [ + {version = "<=2.0.2", python = "<3.10"}, + {version = "^2.0", python = ">=3.10"} +] python = ">=3.9.2, <4.0" python-dateutil = "^2.9.0.post0" pytz = "^2024.1" @@ -61,7 +65,6 @@ pandas = ">= 2.2.0, < 3" pandas-stubs = ">= 2.0.0, <3" plotly = ">= 5.18.0, < 6" polars = "^0.20.15" -pyarrow = ">= 15.0.0, < 16.0.0" pytest = ">= 7.0.0, < 8" pytest-asyncio = ">= 0.23.4, < 1" ruff = "^0.3.4" diff --git a/src/ui/package.json b/src/ui/package.json index 9787cafb2..a4071b3cb 100644 --- a/src/ui/package.json +++ b/src/ui/package.json @@ -25,6 +25,7 @@ "@googlemaps/js-api-loader": "^1.16.6", "@monaco-editor/loader": "^1.3.3", "@tato30/vue-pdf": "^1.9.6", + "ajv": "^8.17.1", "arquero": "^5.2.0", "chroma-js": "^2.4.2", "fuse.js": "7.0.0", @@ -55,10 +56,14 @@ "@typescript-eslint/eslint-plugin": "7.18.0", "@vitejs/plugin-vue": "^5.0.4", "@vue/eslint-config-prettier": "^9.0.0", + "@vue/test-utils": "^2.4.6", "eslint": "^8.39.0", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-storybook": "0.8.0", "eslint-plugin-vue": "^9.28.0", + "postcss": "^8.4.49", + "postcss-assign-layer": "^0.4.0", + "jsdom": "^25.0.1", "prettier": "3.2.5", "storybook": "8.0.5", "vite": "^5.2.7", diff --git a/src/ui/src/builder/BuilderApp.vue b/src/ui/src/builder/BuilderApp.vue index 739e924ec..6c8ee814f 100644 --- a/src/ui/src/builder/BuilderApp.vue +++ b/src/ui/src/builder/BuilderApp.vue @@ -13,7 +13,7 @@ @@ -68,6 +68,7 @@ + @@ -75,13 +76,15 @@ import { computed, defineAsyncComponent, inject, onMounted } from "vue"; import { useDragDropComponent } from "./useDragDropComponent"; import { useComponentActions } from "./useComponentActions"; -import injectionKeys from "../injectionKeys"; -import { isPlatformMac } from "../core/detectPlatform"; +import injectionKeys from "@/injectionKeys"; +import { isPlatformMac } from "@/core/detectPlatform"; import BuilderHeader from "./BuilderHeader.vue"; import BuilderTooltip from "./BuilderTooltip.vue"; import BuilderAsyncLoader from "./BuilderAsyncLoader.vue"; import BuilderPanelSwitcher from "./panels/BuilderPanelSwitcher.vue"; import { WDS_CSS_PROPERTIES } from "@/wds/tokens"; +import { SelectionStatus } from "./builderManager"; +import BuilderToasts from "./BuilderToasts.vue"; const BuilderSettings = defineAsyncComponent({ loader: () => import("./settings/BuilderSettings.vue"), @@ -139,7 +142,7 @@ const { } = useComponentActions(wf, ssbm); const builderMode = computed(() => ssbm.getMode()); -const selectedId = computed(() => ssbm.getSelection()?.componentId); +const selectedId = computed(() => ssbm.firstSelectedId.value); function handleKeydown(ev: KeyboardEvent): void { if (ev.key == "Escape") { @@ -162,9 +165,12 @@ function handleKeydown(ev: KeyboardEvent): void { return; } - if (!ssbm.isSelectionActive()) return; + if (!ssbm.isSingleSelectionActive.value || !ssbm.firstSelectedItem.value) { + return; + } + const { componentId: selectedId, instancePath: selectedInstancePath } = - ssbm.getSelection(); + ssbm.firstSelectedItem.value; if (ev.key == "Delete") { if (!isDeleteAllowed(selectedId)) return; @@ -234,11 +240,20 @@ function handleRendererClick(ev: PointerEvent): void { if (!targetEl) return; const targetId = targetEl.dataset.writerId; const targetInstancePath = targetEl.dataset.writerInstancePath; - if (targetId !== ssbm.getSelectedId()) { - ev.preventDefault(); - ev.stopPropagation(); - ssbm.setSelection(targetId, targetInstancePath, "click"); + + const isAlreadySelected = ssbm.isComponentIdSelected(targetId); + + if ( + isAlreadySelected && + ssbm.selectionStatus.value !== SelectionStatus.Multiple + ) { + return; } + + ev.preventDefault(); + ev.stopPropagation(); + + ssbm.handleSelectionFromEvent(ev, targetId, targetInstancePath, "click"); } const handleRendererDragStart = (ev: DragEvent) => { @@ -251,6 +266,12 @@ const handleRendererDragStart = (ev: DragEvent) => { const componentId = targetEl.dataset.writerId; const { type } = wf.getComponentById(componentId); + // we don't support yet dragginfg multiple components in UI. If drag is starting with multiple selections, we select only one component + if (ssbm.selectionStatus.value === SelectionStatus.Multiple) { + ssbm.setSelection(componentId, undefined, "click"); + ssbm.isSettingsBarCollapsed.value = true; + } + ev.dataTransfer.setData( `application/json;writer=${type},${componentId}`, "{}", diff --git a/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue b/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue index 7c348f1ac..b734d088e 100644 --- a/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue +++ b/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue @@ -21,7 +21,7 @@ const props = defineProps<{ disabled?: boolean; }>(); -const { modelValue, disabled } = toRefs(props); +const { modelValue, disabled, language } = toRefs(props); const emit = defineEmits(["update:modelValue"]); const VARIANTS_SETTINGS: Record< @@ -55,6 +55,10 @@ watch(modelValue, (newCode) => { editor.getModel().setValue(newCode); }); +watch(language, () => { + monaco.editor.setModelLanguage(editor.getModel(), language.value); +}); + onMounted(() => { editor = monaco.editor.create(editorContainerEl.value, { value: modelValue.value, diff --git a/src/ui/src/builder/BuilderHeader.vue b/src/ui/src/builder/BuilderHeader.vue index 67e08d202..e58f2de2c 100644 --- a/src/ui/src/builder/BuilderHeader.vue +++ b/src/ui/src/builder/BuilderHeader.vue @@ -64,7 +64,7 @@ import { Ref, computed, inject, ref } from "vue"; import BuilderSwitcher from "./BuilderSwitcher.vue"; import { useComponentActions } from "./useComponentActions"; import BuilderModal, { ModalAction } from "./BuilderModal.vue"; -import injectionKeys from "../injectionKeys"; +import injectionKeys from "@/injectionKeys"; import BuilderStateExplorer from "./BuilderStateExplorer.vue"; const syncHealthIcon = ref(null); diff --git a/src/ui/src/builder/BuilderMoreDropdown.vue b/src/ui/src/builder/BuilderMoreDropdown.vue new file mode 100644 index 000000000..020d20dff --- /dev/null +++ b/src/ui/src/builder/BuilderMoreDropdown.vue @@ -0,0 +1,83 @@ + + + + + + + diff --git a/src/ui/src/builder/BuilderSelect.vue b/src/ui/src/builder/BuilderSelect.vue index 5eb9ca793..526f2f376 100644 --- a/src/ui/src/builder/BuilderSelect.vue +++ b/src/ui/src/builder/BuilderSelect.vue @@ -8,7 +8,13 @@ {{ currentIcon }} -
{{ currentLabel }}
+
+ {{ currentLabel }} +
{{ expandIcon }}
@@ -141,6 +147,7 @@ function onSelect(value: string) { overflow: hidden; flex-grow: 1; text-align: left; + white-space: nowrap; } .BuilderSelect__trigger__arrow { border: none; diff --git a/src/ui/src/builder/BuilderSwitcher.vue b/src/ui/src/builder/BuilderSwitcher.vue index eb9865226..1d3aa15e5 100644 --- a/src/ui/src/builder/BuilderSwitcher.vue +++ b/src/ui/src/builder/BuilderSwitcher.vue @@ -30,7 +30,7 @@ + + diff --git a/src/ui/src/builder/BuilderTooltip.vue b/src/ui/src/builder/BuilderTooltip.vue index cbca66472..513914da6 100644 --- a/src/ui/src/builder/BuilderTooltip.vue +++ b/src/ui/src/builder/BuilderTooltip.vue @@ -43,7 +43,8 @@ async function setUpAndShowTooltip() { const gapPx = trackedElement.dataset.writerTooltipGap ? parseInt(trackedElement.dataset.writerTooltipGap) : DEFAULT_GAP_PX; - isActive.value = true; + isActive.value = canBeActive(trackedElement); + if (!isActive.value) return; await nextTick(); const { x, y, width, height } = trackedElement.getBoundingClientRect(); const { width: tooltipWidth, height: tooltipHeight } = @@ -69,6 +70,12 @@ async function setUpAndShowTooltip() { } } +function canBeActive(el: HTMLElement) { + const strategy = el.getAttribute("data-writer-tooltip-strategy"); + if (strategy === "overflow") return el.scrollWidth > el.clientWidth; + return true; +} + function handleMouseover(ev: MouseEvent) { const el = ev.target as HTMLElement; @@ -98,6 +105,7 @@ async function confirmTooltip(el: HTMLElement) { attributeFilter: [ "data-writer-tooltip", "data-writer-tooltip-placement", + "data-writer-tooltip-strategy", ], }); setUpAndShowTooltip(); @@ -131,6 +139,7 @@ onUnmounted(() => { display: content; max-width: 260px; filter: drop-shadow(0px 0px 12px rgba(0, 0, 0, 0.16)); + word-break: break-all; } .arrow { diff --git a/src/ui/src/builder/BuilderTree.vue b/src/ui/src/builder/BuilderTree.vue new file mode 100644 index 000000000..a1fe46263 --- /dev/null +++ b/src/ui/src/builder/BuilderTree.vue @@ -0,0 +1,169 @@ + + + + + diff --git a/src/ui/src/builder/builderManager.spec.ts b/src/ui/src/builder/builderManager.spec.ts new file mode 100644 index 000000000..8b30d4d98 --- /dev/null +++ b/src/ui/src/builder/builderManager.spec.ts @@ -0,0 +1,92 @@ +import { describe, it, expect } from "vitest"; +import { generateBuilderManager, SelectionStatus } from "./builderManager"; + +describe(generateBuilderManager.name, () => { + describe("selection", () => { + it("should select an element", () => { + const { + setSelection, + isComponentIdSelected, + selectionStatus, + firstSelectedId, + } = generateBuilderManager(); + + setSelection("componentId", "instancePath", "click"); + + expect(firstSelectedId.value).toBe("componentId"); + expect(isComponentIdSelected("componentId")).toBeTruthy(); + expect(selectionStatus.value).toBe(SelectionStatus.Single); + }); + + it("should select multiple element", () => { + const { + setSelection, + appendSelection, + isComponentIdSelected, + selectionStatus, + firstSelectedId, + } = generateBuilderManager(); + + setSelection("componentId", "instancePath", "click"); + appendSelection("componentId2", "instancePath2", "click"); + + expect(firstSelectedId.value).toBe("componentId"); + expect(isComponentIdSelected("componentId")).toBeTruthy(); + expect(isComponentIdSelected("componentId2")).toBeTruthy(); + expect(selectionStatus.value).toBe(SelectionStatus.Multiple); + }); + + it("should clear the selection an element", () => { + const { + setSelection, + isComponentIdSelected, + selectionStatus, + firstSelectedId, + } = generateBuilderManager(); + + setSelection("componentId", "instancePath", "click"); + setSelection(null); + + expect(firstSelectedId.value).toBeUndefined(); + expect(isComponentIdSelected("componentId")).toBeFalsy(); + expect(selectionStatus.value).toBe(SelectionStatus.None); + }); + + it("should handle click events", () => { + const { + handleSelectionFromEvent, + isComponentIdSelected, + selectionStatus, + } = generateBuilderManager(); + + handleSelectionFromEvent( + { ctrlKey: true } as KeyboardEvent, + "1", + "path", + ); + + expect(selectionStatus.value).toBe(SelectionStatus.Single); + expect(isComponentIdSelected("1")).toBeTruthy(); + + handleSelectionFromEvent( + { ctrlKey: true } as KeyboardEvent, + "2", + "path", + ); + + expect(selectionStatus.value).toBe(SelectionStatus.Multiple); + expect(isComponentIdSelected("1")).toBeTruthy(); + expect(isComponentIdSelected("2")).toBeTruthy(); + + handleSelectionFromEvent( + { ctrlKey: true } as KeyboardEvent, + "2", + "path", + ); + + expect(selectionStatus.value).toBe(SelectionStatus.Single); + expect(isComponentIdSelected("1")).toBeTruthy(); + expect(isComponentIdSelected("2")).toBeFalsy(); + }); + }); +}); diff --git a/src/ui/src/builder/builderManager.ts b/src/ui/src/builder/builderManager.ts index bc7d0a38e..70d207020 100644 --- a/src/ui/src/builder/builderManager.ts +++ b/src/ui/src/builder/builderManager.ts @@ -1,4 +1,4 @@ -import { ref, Ref } from "vue"; +import { computed, ref, Ref } from "vue"; import { Component, ClipboardOperation } from "@/writerTypes"; export const CANDIDATE_CONFIRMATION_DELAY_MS = 1500; @@ -61,13 +61,19 @@ type LogEntry = { type SelectionSource = "click" | "tree" | "log"; +export const enum SelectionStatus { + None = 0, + Single = 1, + Multiple = 2, +} + type State = { mode: "ui" | "workflows" | "preview"; selection: { componentId: Component["id"]; instancePath: string; source: SelectionSource; - }; + }[]; clipboard: { operation: ClipboardOperation; jsonSubtree: string; @@ -82,7 +88,7 @@ type State = { export function generateBuilderManager() { const initState: State = { mode: "ui", - selection: null, + selection: [], clipboard: null, mutationTransactionsSnapshot: { undo: null, @@ -105,14 +111,27 @@ export function generateBuilderManager() { }; const setSelection = ( - componentId: Component["id"], + componentId: Component["id"] | null, instancePath?: string, source?: SelectionSource, ) => { if (componentId === null) { - state.value.selection = null; + state.value.selection = []; return; } + + if (state.value.selection.length !== 0) { + state.value.selection = []; + } + + appendSelection(componentId, instancePath, source); + }; + + const appendSelection = ( + componentId: Component["id"], + instancePath?: string, + source?: SelectionSource, + ) => { let resolvedInstancePath = instancePath; if (typeof resolvedInstancePath == "undefined") { const componentFirstElement: HTMLElement = document.querySelector( @@ -122,25 +141,57 @@ export function generateBuilderManager() { componentFirstElement?.dataset.writerInstancePath; } - state.value.selection = { + state.value.selection.push({ componentId, instancePath: resolvedInstancePath, source, - }; + }); }; - const isSelectionActive = () => { - return state.value.selection !== null; + const handleSelectionFromEvent = ( + ev: MouseEvent | KeyboardEvent, + componentId: Component["id"], + instancePath?: string, + source?: SelectionSource, + ) => { + if (!ev.shiftKey && !ev.ctrlKey && !ev.metaKey) { + return setSelection(componentId, instancePath, source); + } + + if (isComponentIdSelected(componentId)) { + removeSelectedComponentId(componentId); + } else { + appendSelection(componentId, instancePath, source); + } }; - const getSelection = () => { - return state.value.selection; + const isComponentIdSelected = (componentId: string) => { + return state.value.selection.some((s) => s.componentId === componentId); }; - const getSelectedId = () => { - return state.value.selection?.componentId; + const removeSelectedComponentId = (componentId: string) => { + const newSelection = state.value.selection.filter( + (c) => c.componentId !== componentId, + ); + if (newSelection.length === state.value.selection.length) return; + state.value.selection = newSelection; }; + const selectionStatus = computed(() => { + if (state.value.selection.length === 0) return SelectionStatus.None; + if (state.value.selection.length === 1) return SelectionStatus.Single; + return SelectionStatus.Multiple; + }); + const isSingleSelectionActive = computed( + () => selectionStatus.value === SelectionStatus.Single, + ); + + const firstSelectedItem = computed(() => state.value.selection[0]); + + const firstSelectedId = computed( + () => state.value.selection[0]?.componentId, + ); + const setClipboard = (clipboard: State["clipboard"]) => { state.value.clipboard = clipboard; }; @@ -312,10 +363,16 @@ export function generateBuilderManager() { getMode, openPanels: ref(new Set<"code" | "log">()), isSettingsBarCollapsed: ref(false), - isSelectionActive, + isComponentIdSelected, + selectionStatus, + isSingleSelectionActive, + firstSelectedId, + firstSelectedItem, + removeSelectedComponentId, setSelection, - getSelection, - getSelectedId, + appendSelection, + handleSelectionFromEvent, + selection: computed(() => state.value.selection), setClipboard, getClipboard, openMutationTransaction, diff --git a/src/ui/src/builder/panels/BuilderCodePanel.vue b/src/ui/src/builder/panels/BuilderCodePanel.vue index 129acd83c..96ebfb310 100644 --- a/src/ui/src/builder/panels/BuilderCodePanel.vue +++ b/src/ui/src/builder/panels/BuilderCodePanel.vue @@ -3,121 +3,270 @@ panel-id="code" name="Code" :contents-teleport-el="contentsTeleportEl" - :actions="actions" + :actions="[]" :scrollable="false" + enable-left-panel keyboard-shortcut-key="J" class="BuilderCodePanel" + @openned="onOpenPanel" > + + /> diff --git a/src/ui/src/builder/panels/BuilderCodePanelSourceFilesTree.vue b/src/ui/src/builder/panels/BuilderCodePanelSourceFilesTree.vue new file mode 100644 index 000000000..f6778f883 --- /dev/null +++ b/src/ui/src/builder/panels/BuilderCodePanelSourceFilesTree.vue @@ -0,0 +1,112 @@ + + + + + + + diff --git a/src/ui/src/builder/panels/BuilderPanel.vue b/src/ui/src/builder/panels/BuilderPanel.vue index 58bad6f92..bce74befa 100644 --- a/src/ui/src/builder/panels/BuilderPanel.vue +++ b/src/ui/src/builder/panels/BuilderPanel.vue @@ -1,6 +1,6 @@ @@ -76,8 +80,6 @@ async function addWorkflow() { diff --git a/src/ui/src/builder/sidebar/BuilderSidebarPanel.vue b/src/ui/src/builder/sidebar/BuilderSidebarPanel.vue index 2d4bc4457..a8c637a62 100644 --- a/src/ui/src/builder/sidebar/BuilderSidebarPanel.vue +++ b/src/ui/src/builder/sidebar/BuilderSidebarPanel.vue @@ -1,6 +1,6 @@ @@ -25,17 +28,21 @@ defineProps<{ diff --git a/src/ui/src/components/core/base/BaseDropdown.vue b/src/ui/src/components/core/base/BaseDropdown.vue index 32d62e905..7af39fc26 100644 --- a/src/ui/src/components/core/base/BaseDropdown.vue +++ b/src/ui/src/components/core/base/BaseDropdown.vue @@ -1,5 +1,7 @@ diff --git a/src/ui/src/components/core/base/BaseMarkdownRaw.vue b/src/ui/src/components/core/base/BaseMarkdownRaw.vue new file mode 100644 index 000000000..fd2ccfe0b --- /dev/null +++ b/src/ui/src/components/core/base/BaseMarkdownRaw.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/ui/src/components/core/content/CoreAnnotatedText.spec.ts b/src/ui/src/components/core/content/CoreAnnotatedText.spec.ts new file mode 100644 index 000000000..0c5ff93f5 --- /dev/null +++ b/src/ui/src/components/core/content/CoreAnnotatedText.spec.ts @@ -0,0 +1,91 @@ +import { describe, expect, it } from "vitest"; +import BaseMarkdownRaw from "../base/BaseMarkdownRaw.vue"; +import CoreAnnotatedText from "./CoreAnnotatedText.vue"; +import VueDOMPurifyHTML from "vue-dompurify-html"; +import injectionKeys from "@/injectionKeys"; +import { buildMockCore, mockProvides } from "@/tests/mocks"; +import { flushPromises, shallowMount } from "@vue/test-utils"; +import { ref } from "vue"; +import { WdsColor } from "@/wds/tokens"; + +describe("CoreAnnotatedText", async () => { + const text = [ + "# This\n\n", + ["**is**", "Verb", "red"], + " some ", + ["_annotated_", "Adjective"], + ["text", "Noun"], + ". ", + "## title 2\n\n", + "And [here](https://google.com)'s paragraph 2", + ]; + + it("should render in non-markdown mode", async () => { + const { core } = buildMockCore(); + + const wrapper = shallowMount(CoreAnnotatedText, { + global: { + plugins: [VueDOMPurifyHTML], + provide: { + ...mockProvides, + [injectionKeys.core as symbol]: core, + [injectionKeys.isBeingEdited as symbol]: ref(false), + [injectionKeys.evaluatedFields as symbol]: { + text: ref(text), + seed: ref(1), + useMarkdown: ref("no"), + rotateHue: ref("yes"), + referenceColor: ref(WdsColor.Blue5), + copyButtons: ref("yes"), + }, + }, + }, + }); + + await flushPromises(); + + const annotations = wrapper.findAll(".CoreAnnotatedText__annotation"); + expect(annotations).toHaveLength(text.filter(Array.isArray).length); + + // should use the value provided + expect(annotations.at(0).attributes().style).toBe( + "background-color: red;", + ); + + // should generate the color + expect(annotations.at(1).attributes().style).toMatchInlineSnapshot( + `"background-color: rgb(180, 237, 238);"`, + ); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it("should render in markdown mode", async () => { + const { core } = buildMockCore(); + + const wrapper = shallowMount(CoreAnnotatedText, { + global: { + plugins: [VueDOMPurifyHTML], + provide: { + ...mockProvides, + [injectionKeys.core as symbol]: core, + [injectionKeys.isBeingEdited as symbol]: ref(false), + [injectionKeys.evaluatedFields as symbol]: { + text: ref(text), + seed: ref(1), + useMarkdown: ref("yes"), + rotateHue: ref("yes"), + referenceColor: ref(WdsColor.Blue5), + copyButtons: ref("yes"), + }, + }, + }, + }); + + await flushPromises(); + + expect( + wrapper.getComponent(BaseMarkdownRaw).props().rawMarkdown, + ).toMatchSnapshot(); + }); +}); diff --git a/src/ui/src/components/core/content/CoreAnnotatedText.vue b/src/ui/src/components/core/content/CoreAnnotatedText.vue index b89a9bb03..6cc6e584e 100644 --- a/src/ui/src/components/core/content/CoreAnnotatedText.vue +++ b/src/ui/src/components/core/content/CoreAnnotatedText.vue @@ -1,27 +1,36 @@ diff --git a/src/ui/src/components/shared/SharedImgWithFallback.spec.ts b/src/ui/src/components/shared/SharedImgWithFallback.spec.ts new file mode 100644 index 000000000..846fc944f --- /dev/null +++ b/src/ui/src/components/shared/SharedImgWithFallback.spec.ts @@ -0,0 +1,37 @@ +import { describe, it, expect, vi, beforeEach, Mock } from "vitest"; +import SharedImgWithFallback from "./SharedImgWithFallback.vue"; +import { flushPromises, shallowMount } from "@vue/test-utils"; + +describe("SharedImgWithFallback", () => { + let fetch: Mock; + + beforeEach(() => { + fetch = vi.fn().mockResolvedValue({ + ok: true, + headers: new Map([["Content-Type", "image/png"]]), + }); + global.fetch = fetch; + }); + + it("should use the last image because the first two are not valid", async () => { + fetch + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce({ + ok: true, + headers: new Map([["Content-Type", "text/html"]]), + }) + .mockResolvedValue({ + ok: true, + headers: new Map([["Content-Type", "image/png"]]), + }); + + const wrapper = shallowMount(SharedImgWithFallback, { + props: { urls: ["/img1.svg", "/img2.svg", "/img3.svg"] }, + }); + expect(wrapper.get("img").attributes().src).toBe(""); + + await flushPromises(); + + expect(wrapper.get("img").attributes().src).toBe("/img3.svg"); + }); +}); diff --git a/src/ui/src/components/shared/SharedImgWithFallback.vue b/src/ui/src/components/shared/SharedImgWithFallback.vue new file mode 100644 index 000000000..d4b0d7b47 --- /dev/null +++ b/src/ui/src/components/shared/SharedImgWithFallback.vue @@ -0,0 +1,31 @@ + + + diff --git a/src/ui/src/components/shared/SharedJsonViewer/SharedJsonViewer.spec.ts b/src/ui/src/components/shared/SharedJsonViewer/SharedJsonViewer.spec.ts new file mode 100644 index 000000000..c334aa088 --- /dev/null +++ b/src/ui/src/components/shared/SharedJsonViewer/SharedJsonViewer.spec.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from "vitest"; +import SharedJsonViewer from "./SharedJsonViewer.vue"; +import SharedJsonViewerValue from "./SharedJsonViewerValue.vue"; +import SharedJsonViewerObject from "./SharedJsonViewerObject.vue"; +import { mount } from "@vue/test-utils"; + +describe("SharedJsonViewer", () => { + it("should render a string", () => { + const wrapper = mount(SharedJsonViewer, { + propsData: { data: "string" }, + }); + const jsonValue = wrapper.getComponent(SharedJsonViewerValue); + expect(jsonValue.text()).toBe('"string"'); + }); + + it("should render a boolean", () => { + const wrapper = mount(SharedJsonViewer, { propsData: { data: true } }); + const jsonValue = wrapper.getComponent(SharedJsonViewerValue); + expect(jsonValue.text()).toBe("true"); + }); + + it("should render a null", () => { + const wrapper = mount(SharedJsonViewer, { propsData: { data: null } }); + const jsonValue = wrapper.getComponent(SharedJsonViewerValue); + expect(jsonValue.text()).toBe("null"); + }); + + it("should render an object", () => { + const data = { a: "a", b: "b" }; + const wrapper = mount(SharedJsonViewer, { propsData: { data } }); + const jsonObject = wrapper.getComponent(SharedJsonViewerObject); + expect(jsonObject.props().data).toStrictEqual(data); + + expect(wrapper.findAllComponents(SharedJsonViewerValue)).toHaveLength( + 2, + ); + }); + + it("should render a nested object", async () => { + const data = { array: [1, 2, 3], nested: { foo: "bar", a: { b: 2 } } }; + const wrapper = mount(SharedJsonViewer, { propsData: { data } }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it("should render an array", async () => { + const data = [1, 2, 3]; + const wrapper = mount(SharedJsonViewer, { propsData: { data } }); + + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/src/ui/src/components/shared/SharedJsonViewer/SharedJsonViewerObject.spec.ts b/src/ui/src/components/shared/SharedJsonViewer/SharedJsonViewerObject.spec.ts new file mode 100644 index 000000000..c18a087b6 --- /dev/null +++ b/src/ui/src/components/shared/SharedJsonViewer/SharedJsonViewerObject.spec.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import SharedJsonViewerObject from "./SharedJsonViewerObject.vue"; +import { flushPromises, mount } from "@vue/test-utils"; +import SharedJsonViewerCollapsible from "./SharedJsonViewerCollapsible.vue"; +import SharedJsonViewer from "./SharedJsonViewer.vue"; + +describe("SharedJsonViewerObject", () => { + it("should expand a key", async () => { + const data = { array: [1, 2, 3], obj: { a: 1, b: 2, c: 3 } }; + const wrapper = mount(SharedJsonViewerObject, { props: { data } }); + + const collapsers = wrapper.findAllComponents( + SharedJsonViewerCollapsible, + ); + expect(collapsers).toHaveLength(2); + + const arrayCollapser = collapsers.at(0); + + expect(arrayCollapser.props().open).toBeFalsy(); + + arrayCollapser.vm.$emit("toggle", true); + await flushPromises(); + + expect(arrayCollapser.props().open).toBeTruthy(); + + const arrayElement = wrapper.getComponent(SharedJsonViewer); + expect(arrayElement.props().data).toStrictEqual(data.array); + expect(arrayElement.props().path).toStrictEqual(["array"]); + }); +}); diff --git a/src/ui/src/components/shared/SharedJsonViewer/__snapshots__/SharedJsonViewer.spec.ts.snap b/src/ui/src/components/shared/SharedJsonViewer/__snapshots__/SharedJsonViewer.spec.ts.snap new file mode 100644 index 000000000..fa03a91c0 --- /dev/null +++ b/src/ui/src/components/shared/SharedJsonViewer/__snapshots__/SharedJsonViewer.spec.ts.snap @@ -0,0 +1,288 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`SharedJsonViewer > should render a nested object 1`] = ` +
+ + +
+ + + +
+ + + Object{2} + +
+ +
+
+ +
+ +
+ + + + + +
+ + + +
+ + array + + + Array[3] + +
+ +
+
+ +
+ + +
+ +
+
+ + + + + + +
+ + + +
+ + nested + + + Object{2} + +
+ +
+
+ +
+ + +
+ +
+
+ + + +
+ +
+ +
+
+ + + +
+`; + +exports[`SharedJsonViewer > should render an array 1`] = ` +
+ + +
+ + + +
+ + + Array[3] + +
+ +
+
+ +
+ +
+ + + +
+ + 0 + + : + + 1 + +
+ + + +
+ + 1 + + : + + 2 + +
+ + + +
+ + 2 + + : + + 3 + +
+ + +
+ +
+ +
+
+ + + +
+`; diff --git a/src/ui/src/components/workflows/WorkflowsWorkflow.vue b/src/ui/src/components/workflows/WorkflowsWorkflow.vue index 8e610f219..e494fb53d 100644 --- a/src/ui/src/components/workflows/WorkflowsWorkflow.vue +++ b/src/ui/src/components/workflows/WorkflowsWorkflow.vue @@ -19,8 +19,8 @@ :is-selected="selectedArrow == arrowId" :is-engaged=" selectedArrow == arrowId || - wfbm.getSelectedId() == arrow.fromNodeId || - wfbm.getSelectedId() == arrow.toNodeId + wfbm.isComponentIdSelected(arrow.fromNodeId) || + wfbm.isComponentIdSelected(arrow.toNodeId) " @click="handleArrowClick($event, arrowId)" @delete="handleArrowDeleteClick(arrow)" @@ -137,6 +137,7 @@ import { onMounted, onUnmounted, ref, + shallowRef, } from "vue"; import { useComponentActions } from "@/builder/useComponentActions"; import { useDragDropComponent } from "@/builder/useDragDropComponent"; @@ -150,12 +151,12 @@ const nodeContainerEl: Ref = ref(null); const wf = inject(injectionKeys.core); const wfbm = inject(injectionKeys.builderManager); const arrows: Ref = ref([]); -const renderOffset = ref({ x: 0, y: 0 }); +const renderOffset = shallowRef({ x: 0, y: 0 }); const isRunning = ref(false); const selectedArrow = ref(null); const zoomLevel = ref(ZOOM_SETTINGS.initialLevel); const arrowRefresherObserver = new MutationObserver(refreshArrows); -const temporaryNodeCoordinates = ref< +const temporaryNodeCoordinates = shallowRef< Record >({}); @@ -170,7 +171,6 @@ const { createAndInsertComponent, addOut, removeOut, - changeCoordinates, changeCoordinatesMultiple, } = useComponentActions(wf, wfbm); const { getComponentInfoFromDrag } = useDragDropComponent(wf); @@ -484,12 +484,8 @@ function clearActiveOperations() { } function saveNodeMove() { - const { nodeId } = activeNodeMove.value; - const tempXY = temporaryNodeCoordinates.value?.[nodeId]; - if (!tempXY) return; - const { x, y } = tempXY; - changeCoordinates(nodeId, x, y); - temporaryNodeCoordinates.value[nodeId] = null; + changeCoordinatesMultiple(temporaryNodeCoordinates.value); + temporaryNodeCoordinates.value = {}; } function moveNode(ev: MouseEvent) { @@ -500,9 +496,29 @@ function moveNode(ev: MouseEvent) { const newX = Math.floor(x - offset.x); const newY = Math.floor(y - offset.y); - temporaryNodeCoordinates.value[nodeId] = { - x: newX, - y: newY, + const component = wf.getComponentById(nodeId); + + const translationX = newX - component.x; + const translationY = newY - component.y; + + // apply the same vector to other selected components + const otherSelectedComponents = wfbm.selection.value + .map((c) => wf.getComponentById(c.componentId)) + .filter( + (c) => c.id !== nodeId && c.x !== undefined && c.y !== undefined, + ) + .reduce>((acc, component) => { + acc[component.id] = { + x: component.x + translationX, + y: component.y + translationY, + }; + return acc; + }, {}); + + temporaryNodeCoordinates.value = { + ...temporaryNodeCoordinates.value, + ...otherSelectedComponents, + [nodeId]: { x: newX, y: newY }, }; } @@ -713,18 +729,14 @@ async function resetZoom() { changeRenderOffset(0, 0); } -watch( - () => wfbm.getSelection(), - (newSelection) => { - if (!newSelection) return; - selectedArrow.value = null; - if (!wf.isChildOf(workflowComponentId, newSelection.componentId)) - return; - if (newSelection.source !== "click") { - findAndCenterBlock(newSelection.componentId); - } - }, -); +watch(wfbm.firstSelectedItem, (newSelection) => { + if (!newSelection) return; + selectedArrow.value = null; + if (!wf.isChildOf(workflowComponentId, newSelection.componentId)) return; + if (newSelection.source !== "click") { + findAndCenterBlock(newSelection.componentId); + } +}); onMounted(async () => { await resetZoom(); diff --git a/src/ui/src/components/workflows/abstract/WorkflowsNode.vue b/src/ui/src/components/workflows/abstract/WorkflowsNode.vue index d2e6f1163..757e4ec81 100644 --- a/src/ui/src/components/workflows/abstract/WorkflowsNode.vue +++ b/src/ui/src/components/workflows/abstract/WorkflowsNode.vue @@ -1,7 +1,7 @@ + + + + diff --git a/src/ui/src/writerTypes.ts b/src/ui/src/writerTypes.ts index 4eb9d554a..6d7d88feb 100644 --- a/src/ui/src/writerTypes.ts +++ b/src/ui/src/writerTypes.ts @@ -1,6 +1,7 @@ import type { Component as VueComponent } from "vue"; import { generateCore } from "./core"; import { generateBuilderManager } from "./builder/builderManager"; +import type { SchemaObject } from "ajv"; export type Core = ReturnType; @@ -50,13 +51,11 @@ export type InstancePathItem = { /** * Details the full path, including all ancestors, of a unique instance of a Component. */ - export type InstancePath = InstancePathItem[]; /** * Defines component structure and behaviour. Included in Component templates. */ - export type WriterComponentDefinitionField = { /** Display name */ name: string; @@ -77,6 +76,7 @@ export type WriterComponentDefinitionField = { category?: FieldCategory; /** Use the value of this field as a CSS variable */ applyStyleVariable?: boolean; + validator?: SchemaObject; }; export type WriterComponentDefinition = { @@ -164,3 +164,27 @@ export type AbstractTemplate = { }; export type TemplateMap = Record; + +export type SourceFilesFile = { + type: "file"; + complete?: boolean; + content: string; +}; + +export type SourceFilesDirectory = { + type: "directory"; + children: Record; +}; + +/** + * Represent a file tree as an object with: + * + * - the key representing the filename + * - the content as a string for a readable text file, or as a recursive `SourceFiles` if it's a directory + * + * @example + * ```json + * { "type": "directory", "children": { "main.py": { "type": "file", "content": "print('hello')", "complete": true } } } + * ``` + */ +export type SourceFiles = SourceFilesDirectory | SourceFilesFile; diff --git a/src/ui/tsconfig.json b/src/ui/tsconfig.json index 42bc7360c..b5313fc35 100644 --- a/src/ui/tsconfig.json +++ b/src/ui/tsconfig.json @@ -3,6 +3,8 @@ "target": "esnext", "module": "esnext", "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, "importHelpers": true, "isolatedModules": true, "noEmit": true, diff --git a/src/ui/vite.config.custom.ts b/src/ui/vite.config.custom.ts index 6b092c704..592e5a91e 100644 --- a/src/ui/vite.config.custom.ts +++ b/src/ui/vite.config.custom.ts @@ -29,7 +29,7 @@ export default defineConfig({ }, }, rollupOptions: { - external: ["vue", "../injectionKeys"], + external: ["vue", "@/injectionKeys"], output: { globals: { vue: "vue", diff --git a/src/ui/vite.config.ts b/src/ui/vite.config.ts index b4e58235a..4fbcd69ba 100644 --- a/src/ui/vite.config.ts +++ b/src/ui/vite.config.ts @@ -2,6 +2,7 @@ import { fileURLToPath, URL } from "url"; import { defineConfig, UserConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import writerPlugin from "./viteWriterPlugin"; +import postcssAssignLayer from "postcss-assign-layer"; // https://vitejs.dev/config/ export default defineConfig({ @@ -11,6 +12,16 @@ export default defineConfig({ define: { WRITER_LIVE_CCT: JSON.stringify("no"), }, + css: { + postcss: { + plugins: [ + // we move all our CSS into Cascade layers to let the user's stylesheets have more priority + postcssAssignLayer([ + { include: "**/*/*.css", layerName: "wf" }, + ]), + ], + }, + }, build: { outDir: "../writer/static", emptyOutDir: true, @@ -20,6 +31,9 @@ export default defineConfig({ "@": fileURLToPath(new URL("./src", import.meta.url)), }, }, + test: { + environment: "jsdom", + }, server: { proxy: { "/api": { diff --git a/src/writer/ai.py b/src/writer/ai.py index 557f6003d..fc3083aca 100644 --- a/src/writer/ai.py +++ b/src/writer/ai.py @@ -101,7 +101,8 @@ class FunctionToolParameterMeta(TypedDict): Literal["object"], Literal["null"] ] - description: str + description: Optional[str] + required: Optional[bool] class FunctionTool(Tool): @@ -1188,7 +1189,7 @@ def _prepare_message(message: 'Conversation.Message') -> WriterAIMessage: if not ("role" in message and "content" in message): raise ValueError("Improper message format") sdk_message = WriterAIMessage( - content=message["content"] or None, + content=message.get("content", None) or "", role=message["role"] ) if msg_name := message.get("name"): @@ -1309,6 +1310,16 @@ def validate_parameters( raise ValueError( f"'type' for parameter '{param_name}' must be a string" ) + + supported_types = { + "string", "number", "integer", "float", + "boolean", "array", "object", "null" + } + if param_info["type"] not in supported_types: + raise ValueError( + f"Unsupported type '{param_info['type']}' " + + f"for parameter '{param_name}'" + ) # Optional 'description' validation (if provided) if ( @@ -1323,6 +1334,36 @@ def validate_parameters( return True + def prepare_parameters(parameters: Dict[str, FunctionToolParameterMeta]) -> Dict: + """ + Prepares the parameters dictionary for a function tool. + + :param parameters: The parameters dictionary to prepare. + :return: The processed parameters dictionary. + """ + processed_params: Dict[str, FunctionToolParameterMeta] = {} + if not parameters: + return processed_params + else: + required_list = [] + for param_name, param_info in parameters.items(): + processed_param = param_info.copy() + # Convert Python numeric types to JSON schema "number" type + if processed_param["type"] in ["float", "integer"]: + processed_param["type"] = "number" + # Check the "required" flag on parameter + if processed_param.get("required", False) is True: + required_list.append(param_name) + processed_params[param_name] = processed_param + + res = { + "type": "object", + "properties": processed_params + } + if required_list: + res["required"] = required_list + return res + def validate_graph_ids(graph_ids: List[str]) -> bool: """ Validates that `graph_ids` is a list of strings. @@ -1416,7 +1457,10 @@ def validate_graph_ids(graph_ids: List[str]) -> bool: "type": "function", "function": { "name": tool_instance["name"], - "parameters": tool_instance["parameters"] + "parameters": + prepare_parameters( + tool_instance["parameters"] + ) } } ) @@ -1781,6 +1825,18 @@ def _process_streaming_tool_calls(self, chunk: Dict): tool_call_arguments ) + def _prepare_received_message_for_history(self, message: ChoiceMessage): + """ + Prepares a received message for adding to the conversation history. + + :param message: The message to prepare. + :return: The prepared message. + """ + raw_message = message.model_dump() + if not raw_message.get("content"): + raw_message["content"] = "" + return raw_message + def _process_response_data( self, response_data: Chat, @@ -1803,7 +1859,7 @@ def _process_response_data( logging.debug( f"Message has tool calls - {message.tool_calls}" ) - self += message.model_dump() + self += self._prepare_received_message_for_history(message) self._process_tool_calls(message) self.messages += self._gather_tool_calls_results() # Send follow-up call to LLM @@ -2102,6 +2158,110 @@ def generate_content( ) +class Tools: + SplittingStrategy = Union[ + Literal["llm_split"], + Literal["fast_split"], + Literal["hybrid_split"] + ] + MedicalResponseType = Union[ + Literal["Entities"], + Literal["RxNorm"], + Literal["ICD-10-CM"], + Literal["SNOMED CT"] + ] + + @staticmethod + def _retrieve_tools_accessor(): + return WriterAIManager.acquire_client().tools + + @classmethod + def parse_pdf( + cls, + file_id_or_file: Union[str, File, Dict], + format: Union[Literal['text'], Literal['markdown']] = 'text', + config: Optional[APIOptions] = None + ) -> str: + config = config or {} + client_tools = cls._retrieve_tools_accessor() + file_id = None + if isinstance(file_id_or_file, File): + file_id = file_id_or_file.id + elif isinstance(file_id_or_file, Dict): + if not ( + "data" in file_id_or_file + and + "type" in file_id_or_file + and + "name" in file_id_or_file + ): + raise ValueError( + "Invalid payload passed to parse_pdf method: " + + "expected keys `data`, `type` and `name`, " + + f"got {file_id_or_file.keys()}" + ) + new_file_from_payload = upload_file( + **file_id_or_file, + config=config + ) + file_id = new_file_from_payload.id + elif isinstance(file_id_or_file, str): + file_id = file_id_or_file + else: + raise ValueError( + "parse_pdf expects a `writer.ai.File` type instance, " + + f"a file payload, or a string ID: got {type(file_id_or_file)}" + ) + + result = client_tools.parse_pdf( + file_id=file_id, + format=format, + **config + ) + + return result.content + + @classmethod + def split( + cls, + content: str, + strategy: SplittingStrategy = "llm_split", + config: Optional[APIOptions] = None + ) -> List[str]: + if not content: + raise ValueError("Content cannot be empty.") + config = config or {} + client_tools = cls._retrieve_tools_accessor() + + result = client_tools.context_aware_splitting( + strategy=strategy, + text=content, + **config + ) + + return result.chunks + + @classmethod + def comprehend_medical( + cls, + content: str, + response_type: MedicalResponseType = "Entities", + config: Optional[APIOptions] = None + ) -> List: + if not content: + raise ValueError("Content cannot be empty.") + config = config or {} + client_tools = cls._retrieve_tools_accessor() + + result = client_tools.comprehend.medical( + content=content, + response_type=response_type, + **config + ) + + return result.entities + + def complete( initial_text: str, config: Optional['CreateOptions'] = None @@ -2339,3 +2499,4 @@ def init(token: Optional[str] = None): apps = Apps() +tools = Tools() diff --git a/src/writer/app_runner.py b/src/writer/app_runner.py index 5531a59ce..6db5d9c0b 100644 --- a/src/writer/app_runner.py +++ b/src/writer/app_runner.py @@ -7,7 +7,9 @@ import multiprocessing.connection import multiprocessing.synchronize import os +import shutil import signal +import subprocess import sys import threading from types import ModuleType @@ -43,6 +45,7 @@ InitSessionRequestPayload, InitSessionResponsePayload, ServeMode, + SourceFilesDirectory, StateContentRequest, StateContentResponsePayload, StateEnquiryRequest, @@ -501,9 +504,9 @@ class FileEventHandler(watchdog.events.PatternMatchingEventHandler): Watches for changes in files and triggers code reloads. """ - def __init__(self, update_callback: Callable): + def __init__(self, update_callback: Callable, patterns: List[str]): self.update_callback = update_callback - super().__init__(patterns=["*.py"], ignore_patterns=[ + super().__init__(patterns=patterns, ignore_patterns=[ ".*"], ignore_directories=False, case_sensitive=False) def on_any_event(self, event) -> None: @@ -605,6 +608,7 @@ def __init__(self, app_path: str, mode: str): self.client_conn: Optional[multiprocessing.connection.Connection] = None self.app_process: Optional[AppProcess] = None self.run_code: Optional[str] = None + self.source_files: SourceFilesDirectory = {"children": {}, "type": "directory"} self.bmc_components: Optional[Dict] = None self.is_app_process_server_ready = multiprocessing.Event() self.is_app_process_server_failed = multiprocessing.Event() @@ -644,14 +648,37 @@ def _set_logger(self): def _start_fs_observer(self): self.observer = PollingObserver(AppRunner.UPDATE_CHECK_INTERVAL_SECONDS) - self.observer.schedule(FileEventHandler(self.reload_code_from_saved), path=self.app_path, recursive=True) + self.observer.schedule(FileEventHandler(self.reload_code_from_saved, patterns=["*.py"]), path=self.app_path, recursive=True) + self.observer.schedule(FileEventHandler(self._install_requirements, patterns=["requirements.txt"]), path=self.app_path) self.observer.start() def _start_wf_project_process_write_files(self): wf_project.start_process_write_files_async(self.wf_project_context, AppRunner.WF_PROJECT_SAVE_INTERVAL) + def _install_requirements(self) -> None: + logger = logging.getLogger('writer') + logger.debug("\nDetected changes in requirements.txt. Installing dependencies...") + try: + # Run pip install command + subprocess.run( + ["pip", "install", "-r", "requirements.txt"], + check=True, + capture_output=True, + text=True, + # stdout=subprocess.DEVNULL, # Suppress pip output + cwd=self.app_path, + ) + logger.debug("Dependencies installed successfully, restart server!\n") + self.reload_code_from_saved() + except subprocess.CalledProcessError as e: + logger.warning(f"Error installing dependencies: {e.stderr}") + # TODO(WF-170): find a way to dispatch log + except Exception as e: + logger.warning(f"Unexpected error: {e}") + def load(self) -> None: - self.run_code = self._load_persisted_script() + self.run_code = self.load_persisted_script("main.py") + self.source_files = wf_project.build_source_files(self.app_path) self.bmc_components = self._load_persisted_components() if self.mode == "edit": @@ -701,17 +728,68 @@ async def dispatch_message(self, session_id: Optional[str], request: AppProcessS return response - def _load_persisted_script(self) -> str: + + def create_persisted_script(self, file = "main.py"): + path = os.path.join(self.app_path, file) + self._check_file_in_app_path(path) + + with open(path, "x", encoding='utf-8') as f: + f.write('') + + self.source_files = wf_project.build_source_files(self.app_path) + + def rename_persisted_script(self, from_path: str, to_path: str): + if from_path == 'main.py': + raise PermissionError("cannot rename main script") + if to_path == 'main.py': + raise PermissionError("cannot overwrite main script") + + from_path_abs = os.path.join(self.app_path, from_path) + self._check_file_in_app_path(from_path_abs) + + to_path_abs = os.path.join(self.app_path, to_path) + self._check_file_in_app_path(to_path_abs) + + os.makedirs(os.path.dirname(to_path_abs), exist_ok=True) + os.rename(from_path_abs, to_path_abs) + + self.source_files = wf_project.build_source_files(self.app_path) + + def delete_persisted_script(self, file: str): + if file == 'main.py': + raise PermissionError("cannot delete main script") + + path = os.path.join(self.app_path, file) + self._check_file_in_app_path(path) + + if os.path.isfile(path): + os.remove(path) + else: + shutil.rmtree(path) + + self.source_files = wf_project.build_source_files(self.app_path) + + def load_persisted_script(self, file = "main.py") -> str: + path = os.path.join(self.app_path, file) + self._check_file_in_app_path(path) + logger = logging.getLogger('writer') try: contents = None - with open(os.path.join(self.app_path, "main.py"), "r", encoding='utf-8') as f: + with open(path, "r", encoding='utf-8') as f: contents = f.read() return contents - except FileNotFoundError: - logger.error( - "Couldn't find main.py in the path provided: %s.", self.app_path) - sys.exit(1) + except FileNotFoundError as error: + logger.error("Couldn't find %s in the path provided: %s.", file, self.app_path) + if file == "main.py": + sys.exit(1) + else: + raise error + + def _check_file_in_app_path(self, path): + if not os.path.abspath(path).startswith(os.path.abspath((self.app_path))): + raise PermissionError(f"{path} is outside of application ({self.app_path})") + def _load_persisted_components(self) -> Dict[str, ComponentDefinition]: logger = logging.getLogger('writer') @@ -783,13 +861,21 @@ async def handle_state_content(self, session_id: str) -> AppProcessServerRespons type="stateContent" )) - def save_code(self, session_id: str, code: str) -> None: + def save_code(self, session_id: str, code: str, path: List[str] = ['main.py']) -> None: if self.mode != "edit": raise PermissionError("Cannot save code in non-edit mode.") - with open(os.path.join(self.app_path, "main.py"), "w") as f: + filepath = os.path.join(self.app_path, *path) + + # ensure we don't load a file outside of the application (like `../../../etc/passwd`) + if not os.path.abspath(filepath).startswith(self.app_path): + raise FileNotFoundError(f"{filepath} is outside of application ({self.app_path})") + + with open(filepath, "w") as f: f.write(code) + self.source_files = wf_project.build_source_files(self.app_path) + def _clean_process(self) -> None: # Terminate the AppProcess server by sending an empty message # The empty message will bounce an empty message and terminate the client too @@ -861,7 +947,7 @@ def _start_app_process(self) -> None: def reload_code_from_saved(self) -> None: if not self.is_app_process_server_ready.is_set(): return - self.update_code(None, self._load_persisted_script()) + self.update_code(None, self.load_persisted_script()) def update_code(self, session_id: Optional[str], run_code: str) -> None: @@ -876,6 +962,7 @@ def update_code(self, session_id: Optional[str], run_code: str) -> None: if not self.is_app_process_server_ready.is_set(): return self.run_code = run_code + self.source_files = wf_project.build_source_files(self.app_path) self._clean_process() self._start_app_process() self.is_app_process_server_ready.wait() diff --git a/src/writer/blocks/httprequest.py b/src/writer/blocks/httprequest.py index de2642236..e5d9c7a6b 100644 --- a/src/writer/blocks/httprequest.py +++ b/src/writer/blocks/httprequest.py @@ -31,16 +31,33 @@ def register(cls, type: str): "PATCH": "PATCH", "DELETE": "DELETE" }, - "default": "GET" + "default": "GET", + "validator": { + "type": "string", + "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"], + } }, "url": { "name": "URL", "type": "Text", + "validator": { + "type": "string", + "format": "uri", + } }, "headers": { "name": "Headers", "type": "Key-Value", "default": "{}", + "validator": { + "type": "object", + "patternProperties": { + "^.*$": { + "type": ["string", "number", "boolean"], + }, + }, + "additionalProperties": True, + } }, "body": { "name": "Body", diff --git a/src/writer/blocks/runworkflow.py b/src/writer/blocks/runworkflow.py index 00e1189ec..c7c372fae 100644 --- a/src/writer/blocks/runworkflow.py +++ b/src/writer/blocks/runworkflow.py @@ -18,6 +18,10 @@ def register(cls, type: str): "workflowKey": { "name": "Workflow key", "type": "Text", + "validator": { + "type": "string", + "format": "writer#workflowKey", + } }, "payload": { "name": "Payload", diff --git a/src/writer/blocks/writeraddchatmessage.py b/src/writer/blocks/writeraddchatmessage.py index e4d8ebe24..12574a131 100644 --- a/src/writer/blocks/writeraddchatmessage.py +++ b/src/writer/blocks/writeraddchatmessage.py @@ -23,7 +23,15 @@ def register(cls, type: str): "message": { "name": "Message", "type": "Object", - "init": '{ "role": "assistant", "content": "Hello" }' + "init": '{ "role": "assistant", "content": "Hello" }', + "validator": { + "type": "object", + "properties": { + "role": { "type": "string" }, + "content": { "type": "string" }, + }, + "additionalProperties": False, + } } }, "outs": { diff --git a/src/writer/blocks/writeraddtokg.py b/src/writer/blocks/writeraddtokg.py index dcaa415f0..1b84c905e 100644 --- a/src/writer/blocks/writeraddtokg.py +++ b/src/writer/blocks/writeraddtokg.py @@ -20,13 +20,20 @@ def register(cls, type: str): "graphId": { "name": "Graph id", "type": "Text", - "desc": "The id for an existing knowledge graph. It has a UUID format." + "desc": "The id for an existing knowledge graph. It has a UUID format.", + "validator": { + "type": "string", + "format": "uuid", + } }, "files": { "name": "Files", "type": "Object", "default": "[]", - "desc": "A list of files to be uploaded and added to the knowledge graph. You can use files uploaded via the File Input component or specify dictionaries with data, type and name." + "desc": "A list of files to be uploaded and added to the knowledge graph. You can use files uploaded via the File Input component or specify dictionaries with data, type and name.", + "validator": { + "type": "array", + } }, }, "outs": { diff --git a/src/writer/blocks/writercompletion.py b/src/writer/blocks/writercompletion.py index d1980397d..4332efa48 100644 --- a/src/writer/blocks/writercompletion.py +++ b/src/writer/blocks/writercompletion.py @@ -29,7 +29,12 @@ def register(cls, type: str): "temperature": { "name": "Temperature", "type": "Number", - "default": "0.7" + "default": "0.7", + "validator": { + "type": "number", + "minimum": 0, + "maximum": 1, + } } }, "outs": { diff --git a/src/writer/blocks/writerinitchat.py b/src/writer/blocks/writerinitchat.py index de560420d..189002a99 100644 --- a/src/writer/blocks/writerinitchat.py +++ b/src/writer/blocks/writerinitchat.py @@ -30,7 +30,12 @@ def register(cls, type: str): "temperature": { "name": "Temperature", "type": "Number", - "default": "0.7" + "default": "0.7", + "validator": { + "type": "number", + "minimum": 0, + "maximum": 1, + } } }, "outs": { diff --git a/src/writer/blocks/writernocodeapp.py b/src/writer/blocks/writernocodeapp.py index 1f505739d..249658bb3 100644 --- a/src/writer/blocks/writernocodeapp.py +++ b/src/writer/blocks/writernocodeapp.py @@ -18,7 +18,11 @@ def register(cls, type: str): "appId": { "name": "App Id", "type": "Text", - "desc": "The app id can be found in the app's URL. It has a UUID format." + "desc": "The app id can be found in the app's URL. It has a UUID format.", + "validator": { + "type": "string", + "format": "uuid", + } }, "appInputs": { "name": "App inputs", diff --git a/src/writer/serve.py b/src/writer/serve.py index caa310726..1fb8d5f22 100644 --- a/src/writer/serve.py +++ b/src/writer/serve.py @@ -261,6 +261,7 @@ def _get_edit_starter_pack(payload: InitSessionResponsePayload): components=payload.components, userFunctions=payload.userFunctions, runCode=run_code, + sourceFiles=app_runner.source_files, extensionPaths=cached_extension_paths, featureFlags=payload.featureFlags, abstractTemplates=abstract.templates @@ -546,10 +547,36 @@ async def _handle_incoming_edit_message(websocket: WebSocket, session_id: str, r )) elif req_message.type == "codeSaveRequest": app_runner.save_code( - session_id, req_message.payload["code"]) + session_id, req_message.payload["code"], req_message.payload["path"]) elif req_message.type == "codeUpdate": - app_runner.update_code( - session_id, req_message.payload["code"]) + app_runner.update_code(session_id, req_message.payload["code"]) + elif req_message.type == "loadSourceFile": + path = os.path.join(*req_message.payload['path']) + try: + response.payload = { "content": app_runner.load_persisted_script(path) } + except FileNotFoundError as error: + logging.warning(f"could not load script at {path}", error) + response.payload = {"error": str(error)} + elif req_message.type == "createSourceFile": + path = os.path.join(*req_message.payload['path']) + try: + app_runner.create_persisted_script(path) + except Exception as error: + response.payload = {"error": str(error)} + elif req_message.type == "deleteSourceFile": + path = os.path.join(*req_message.payload['path']) + try: + app_runner.delete_persisted_script(path) + except Exception as error: + response.payload = {"error": str(error)} + elif req_message.type == "renameSourceFile": + from_path = os.path.join(*req_message.payload['from']) + to_path = os.path.join(*req_message.payload['to']) + try: + app_runner.rename_persisted_script(from_path, to_path) + except Exception as error: + response.payload = {"error": str(error)} + await websocket.send_json(response.model_dump()) async def _handle_keep_alive_message(websocket: WebSocket, session_id: str, req_message: WriterWebsocketIncoming): diff --git a/src/writer/ss_types.py b/src/writer/ss_types.py index 31191f960..4aff03bfd 100644 --- a/src/writer/ss_types.py +++ b/src/writer/ss_types.py @@ -33,6 +33,19 @@ class AbstractTemplate(BaseModel): baseType: str writer: Dict + +class SourceFilesFile(TypedDict): + type: Literal["file"] + complete: Optional[bool] + content: str + + +class SourceFilesDirectory(TypedDict): + type: Literal["directory"] + children: Dict[str, 'SourceFiles'] + +SourceFiles = Union[SourceFilesFile, SourceFilesDirectory] + # Web server models @@ -59,6 +72,7 @@ class InitResponseBodyRun(InitResponseBody): class InitResponseBodyEdit(InitResponseBody): mode: Literal["edit"] runCode: Optional[str] = None + sourceFiles: SourceFilesDirectory = {"type": "directory", "children": {}} class WriterWebsocketIncoming(BaseModel): @@ -231,3 +245,4 @@ class WorkflowExecutionLog(BaseModel): class WriterConfigurationError(ValueError): pass + diff --git a/src/writer/wf_project.py b/src/writer/wf_project.py index 1357adc22..bf73d5902 100644 --- a/src/writer/wf_project.py +++ b/src/writer/wf_project.py @@ -6,6 +6,7 @@ >>> metadata, components = wf_project.read_files('app/hello') """ import dataclasses +import glob import io import json import logging @@ -19,7 +20,7 @@ from typing import Any, Dict, List, Tuple from writer import core_ui -from writer.ss_types import ComponentDefinition, MetadataDefinition +from writer.ss_types import ComponentDefinition, MetadataDefinition, SourceFilesDirectory ROOTS = ['root', 'workflows_root'] COMPONENT_ROOTS = ['page', 'workflows_workflow'] @@ -321,3 +322,63 @@ def can_create_project(path: str) -> bool: return True return False + + +def build_source_files(app_path: str) -> SourceFilesDirectory: + """ + Build a file tree as `Dict` wherein the key represent the filename. The value is a `Dict` with a `type` as: + + - `directory`, so it's a directory containing `children` + - `file`, so it's a file with `content` as string. We limit the file to the first X characters and set `"complete": False` if content is truncated + + Example: + + >>> {'type': 'directory', 'children': {'README.md': {'type': 'file', 'content': 'This app w', 'complete': False}}} + """ + def load_persisted_script(file: str) -> str: + path = os.path.join(app_path, file) + with open(path, "r", encoding='utf-8') as f: + return f.read() + + files = [] + for root, dirs, filenames in os.walk(app_path): + # ignore specific folders + dirs[:] = [d for d in dirs if d not in {'.venv', 'venv', '__pycache__', '.wf'}] + for filename in filenames: + files.append(os.path.realpath(os.path.join(root, filename))) + + file_tree: SourceFilesDirectory = { + "type": "directory", + "children": {} + } + + + for file in files: + relative_path = os.path.relpath(file, app_path) + parts = relative_path.split(os.sep) + current_level: SourceFilesDirectory = file_tree + + for part in parts: + if os.path.isdir(os.path.join(app_path, *parts[:parts.index(part) + 1])): + if part not in current_level["children"]: + current_level["children"][part] = { "type": "directory", "children": {} } + + next_level = current_level["children"][part] + + if next_level["type"] == "directory": + current_level = next_level + else: + try: + content = load_persisted_script(relative_path) + extension = os.path.splitext(relative_path)[1] + # limit only the first 100 characters to limit bandwidth usage, the rest will be lazy loaded + exerpt = content if extension == '.py' else content[0:100] + current_level["children"][part] = { + "type": "file", + "content": exerpt, + "complete": exerpt == content, + } + except (UnicodeDecodeError, FileNotFoundError): + pass + + return file_tree diff --git a/tests/backend/test_ai.py b/tests/backend/test_ai.py index 819cbe942..9ffab8d4e 100644 --- a/tests/backend/test_ai.py +++ b/tests/backend/test_ai.py @@ -1,21 +1,25 @@ """ # AI Module Test Suite -This module provides a suite of tests for the AI integration with the Writer SDK. +This module provides a suite of tests for the AI integration +with the Writer SDK. ## Types of tests 1. **Mock tests** - - These tests simulate interactions with the Writer AI SDK without making real API calls. + - These tests simulate interactions with the Writer AI SDK without + making real API calls. - They are faster and can be run frequently during development. 2. **SDK query tests** - These tests make real API calls to the Writer AI service. - - They are intended for use on potentially breaking changes and major releases to ensure compatibility with the live API. + - They are intended for use on potentially breaking changes + and major releases to ensure compatibility with the live API. ## Running the tests -By default, SDK query tests are marked with the custom `explicit` decorator and are excluded from regular test runs. +By default, SDK query tests are marked with the custom `explicit` decorator +and are excluded from regular test runs. Only mock tests are run by regular `pytest` command: ```sh @@ -34,7 +38,8 @@ WRITER_API_KEY=your_api_key_here ``` -After that, to include SDK query tests into the run, use the `--full-run` option: +After that, to include SDK query tests into the run, use the `--full-run` +option: ```sh pytest ./tests/backend/test_ai.py --full-run ``` @@ -70,6 +75,7 @@ retrieve_graph, stream_ask, stream_complete, + tools, upload_file, ) from writerai import Writer @@ -80,25 +86,87 @@ ChatCompletionChunk, Completion, StreamingData, + ToolContextAwareSplittingResponse, + ToolParsePdfResponse, ) +from writerai.types.tools import ComprehendMedicalResponse -# Decorator to mark tests as explicit, i.e. that they only to be run on direct demand +# Decorator to mark tests as explicit, +# i.e. that they only to be run on direct demand explicit = pytest.mark.explicit test_complete_literal = "Completed text" +test_expected_pdf_content = "PDF content" +test_expected_chunks = ["chunk1", "chunk2", "chunk3"] +test_expected_medical_entities = [ + { + "type": "Diagnosis", + "text": "Example", + "category": "MEDICAL_CONDITION", + "score": 0.98, + "begin_offset": 0, + "end_offset": 7, + "attributes": [], + "traits": [], + "concepts": [] + } +] + +test_pdf_data = \ + ( + b"%PDF-1.4\n" + b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n" + b"2 0 obj\n<< /Type /Pages /Count 1 /Kids [3 0 R] >>\nendobj\n" + b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] " + b"/Contents 4 0 R >>\nendobj\n" + b"4 0 obj\n<< /Length 44 >>\nstream\nBT\n/F1 24 Tf\n72 144 Td\n" + b"(Hello PDF!)Tj\nET\nendstream\nendobj\n" + b"xref\n0 5\n0000000000 65535 f \n0000000018 00000 n \n" + b"0000000070 00000 n \n0000000121 00000 n \n0000000220 00000 n \n" + b"trailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n316\n%%EOF" + ) +test_split_text = \ + "This is some sample text that will be split into chunks. " + \ + "The quick brown fox jumps over the lazy dog. " + \ + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + \ + "Sed do eiusmod tempor incididunt ut labore " + \ + "et dolore magna aliqua. " + \ + "Ut enim ad minim veniam, quis nostrud " + \ + "exercitation ullamco laboris " + \ + "nisi ut aliquip ex ea commodo consequat. " + \ + "Duis aute irure dolor in reprehenderit in voluptate velit esse " + \ + "cillum dolore eu fugiat nulla pariatur. " + \ + "Excepteur sint occaecat cupidatat non proident, " + \ + "sunt in culpa qui officia deserunt mollit anim id est laborum. " + \ + "These sentences are intended to test how well the split function " + \ + "handles a larger block of text, including punctuation, " + \ + "multiple sentences, and some variety in wording." +test_fast_split_text = \ + "Q: What'\''s the best way to learn a new song for a person that has " + \ + "never learned a song before? A: One way to learn a new song would be " + \ + "to write your own and see how it goes, it may just come natural. A: " + \ + "Alternatively you could practice the same verse over and over until " + \ + "you know it by heart and then move onto the next one, until you know " + \ + "the song start to finish. A: You could also simply read the lyrics " + \ + "while the music is playing in the background, eventually you will " + \ + "learn it. A: It'\''s also very useful to get someone to sing it for " + \ + "you before going to bed. A: And don'\''t forget you can always " + \ + "team up with a friend and practice together, maybe at a karaoke!" @pytest.fixture def mock_app_content_generation(): - with patch('writer.ai.WriterAIManager.acquire_client') as mock_acquire_client: + with patch('writer.ai.WriterAIManager.acquire_client') \ + as mock_acquire_client: original_client = Writer(api_key="fake_token") non_streaming_client = AsyncMock(original_client) mock_acquire_client.return_value = non_streaming_client - non_streaming_client.applications.generate_content.return_value = ApplicationGenerateContentResponse( - suggestion=test_complete_literal - ) + non_streaming_client.applications.generate_content.return_value =\ + ApplicationGenerateContentResponse( + suggestion=test_complete_literal + ) yield non_streaming_client @@ -271,7 +339,12 @@ def sdk_graph_mock(): return SDKGraph( id="test_graph_id", created_at=datetime.now(), - file_status={"completed": 0, "failed": 0, "in_progress": 0, "total": 0}, + file_status={ + "completed": 0, + "failed": 0, + "in_progress": 0, + "total": 0 + }, name="test_graph", description="A test graph" ) @@ -290,7 +363,8 @@ def sdk_file_mock(): @pytest.fixture def mock_graphs_accessor(sdk_file_mock, sdk_graph_mock): - with patch('writer.ai.Graph._retrieve_graphs_accessor') as mock_acquire_client: + with patch('writer.ai.Graph._retrieve_graphs_accessor') \ + as mock_acquire_client: mock_accessor = MagicMock() mock_graph = Graph(sdk_graph_mock) mock_file = File(sdk_file_mock) @@ -298,8 +372,10 @@ def mock_graphs_accessor(sdk_file_mock, sdk_graph_mock): mock_accessor.add_file_to_graph.return_value = mock_file mock_accessor.retrieve.return_value = mock_graph mock_accessor.list.return_value = [mock_graph] - mock_accessor.delete.return_value = GraphDeleteResponse(id="test_file_id", deleted=True) - mock_accessor.remove_file_from_graph.return_value = GraphRemoveFileFromGraphResponse(id="test_file_id", deleted=True) + mock_accessor.delete.return_value =\ + GraphDeleteResponse(id="test_file_id", deleted=True) + mock_accessor.remove_file_from_graph.return_value =\ + GraphRemoveFileFromGraphResponse(id="test_file_id", deleted=True) mock_acquire_client.return_value = mock_accessor yield mock_accessor @@ -307,17 +383,45 @@ def mock_graphs_accessor(sdk_file_mock, sdk_graph_mock): @pytest.fixture def mock_files_accessor(sdk_file_mock): - with patch('writer.ai.File._retrieve_files_accessor') as mock_acquire_client: + with patch('writer.ai.File._retrieve_files_accessor') \ + as mock_acquire_client: mock_accessor = MagicMock() mock_file = File(sdk_file_mock) mock_accessor.retrieve.return_value = mock_file mock_accessor.list.return_value = [mock_file] mock_accessor.upload.return_value = mock_file - mock_accessor.delete.return_value = FileDeleteResponse(id="test_delete", deleted=True) + mock_accessor.delete.return_value =\ + FileDeleteResponse(id="test_delete", deleted=True) mock_acquire_client.return_value = mock_accessor yield mock_accessor +@pytest.fixture +def mock_tools_accessor(): + with patch('writer.ai.WriterAIManager.acquire_client') \ + as mock_acquire_client: + mock_client = MagicMock() + mock_accessor = MagicMock() + mock_acquire_client.return_value = mock_client + + mock_parse_pdf_content = ToolParsePdfResponse( + content=test_expected_pdf_content + ) + mock_split = ToolContextAwareSplittingResponse( + chunks=test_expected_chunks + ) + mock_comprehend_medical = ComprehendMedicalResponse( + entities=test_expected_medical_entities + ) + + mock_accessor.parse_pdf.return_value = mock_parse_pdf_content + mock_accessor.context_aware_splitting.return_value = mock_split + mock_accessor.comprehend.medical.return_value = mock_comprehend_medical + + mock_client.tools = mock_accessor + yield mock_client + + @pytest.fixture def created_graphs(): graphs = [] @@ -341,7 +445,8 @@ def __init__(self, token): def create_fake_app_process(token: str) -> FakeAppProcessForAIManager: """ - Helper function to create and patch FakeAppProcessForAIManager with a given token. + Helper function to create and patch FakeAppProcessForAIManager + with a given token. """ fake_process = FakeAppProcessForAIManager(token) method_to_patch = 'writer.ai.WriterAIManager.acquire_instance' @@ -466,7 +571,11 @@ def test_conversation_serialized_messages_excludes_system(): assert serialized_messages[0] == \ {"role": "user", "content": "Hello", "actions": None} assert serialized_messages[1] == \ - {"role": "assistant", "content": "Hi, how can I help?", "actions": None} + { + "role": "assistant", + "content": "Hi, how can I help?", + "actions": None + } @pytest.mark.set_token("fake_token") @@ -480,7 +589,10 @@ def test_conversation_complete(emulate_app_process, mock_non_streaming_client): @pytest.mark.set_token("fake_token") -def test_conversation_stream_complete(emulate_app_process, mock_streaming_client): +def test_conversation_stream_complete( + emulate_app_process, + mock_streaming_client +): # Create the Conversation object and collect the response chunks conversation = Conversation("Initial prompt") @@ -489,7 +601,10 @@ def test_conversation_stream_complete(emulate_app_process, mock_streaming_client response_chunks.append(chunk) # Verify the content - assert " ".join(chunk["content"] for chunk in response_chunks) == "part1 part2" + assert " ".join( + chunk["content"] + for chunk in response_chunks + ) == "part1 part2" @pytest.mark.set_token("fake_token") @@ -595,6 +710,7 @@ def test_function(arg1): assert "inform the user about the error" in tool_results[0]["content"] assert "inform the user about the error" in tool_results[1]["content"] + @pytest.mark.set_token("fake_token") def test_conversation_with_tool_call_max_depth(emulate_app_process, mock_tool_calls_client): def test_function(arg1): @@ -633,6 +749,7 @@ def test_function(arg1): for _ in response: pass + @pytest.mark.set_token("fake_token") def test_complete(emulate_app_process, mock_non_streaming_client): response = complete("test") @@ -648,7 +765,10 @@ def test_stream_complete(emulate_app_process, mock_streaming_client): @pytest.mark.set_token("fake_token") -def test_generate_content_from_app(emulate_app_process, mock_app_content_generation): +def test_generate_content_from_app( + emulate_app_process, + mock_app_content_generation +): response = apps.generate_content("abc123", { "Favorite animal": "Dog", "Favorite color": "Purple" @@ -750,6 +870,73 @@ def test_delete_file(mock_files_accessor): assert response.deleted is True +@pytest.mark.set_token("fake_token") +def test_tools_parse_pdf_file_id(emulate_app_process, mock_tools_accessor): + file_content = tools.parse_pdf(file_id_or_file="test_file_id") + assert file_content == test_expected_pdf_content + + +@pytest.mark.set_token("fake_token") +def test_tools_parse_pdf_file_payload_valid( + emulate_app_process, + mock_tools_accessor +): + fake_payload = { + "name": "file.pdf", + "type": "application/pdf", + "data": "some base64 encoded pdf data" + } + file_content = tools.parse_pdf(file_id_or_file=fake_payload) + assert file_content == test_expected_pdf_content + + +@pytest.mark.set_token("fake_token") +def test_tools_parse_pdf_file_payload_missing_name( + emulate_app_process, + mock_tools_accessor +): + fake_payload = { + "type": "application/pdf", + "data": "some base64 encoded pdf data" + } + with pytest.raises( + ValueError, + match=r"Invalid payload passed to parse_pdf method: " + + r"expected keys `data`, `type` and `name`, " + + r"got dict_keys\(\['type', 'data'\]\)" + ): + tools.parse_pdf(file_id_or_file=fake_payload) + + +@pytest.mark.set_token("fake_token") +def test_tools_split(emulate_app_process, mock_tools_accessor): + result = tools.split("Some content here") + assert result == test_expected_chunks + + +@pytest.mark.set_token("fake_token") +def test_tools_split_empty(emulate_app_process, mock_tools_accessor): + with pytest.raises(ValueError, match="Content cannot be empty."): + tools.split("") + + +@pytest.mark.set_token("fake_token") +def test_tools_comprehend_medical(emulate_app_process, mock_tools_accessor): + result = tools.comprehend_medical("Medical content") + assert result == ComprehendMedicalResponse( + entities=test_expected_medical_entities + ).entities + + +@pytest.mark.set_token("fake_token") +def test_tools_comprehend_medical_empty( + emulate_app_process, + mock_tools_accessor +): + with pytest.raises(ValueError, match="Content cannot be empty."): + tools.comprehend_medical("") + + @pytest.mark.set_token("fake_token") def test_ask(mock_non_streaming_client): question = "What is the capital of France?" @@ -821,7 +1008,10 @@ def test_stream_ask_graph_class(mock_streaming_client): @explicit def test_explicit_conversation_complete(emulate_app_process): conversation = Conversation() - conversation.add("user", "Hello, how can I improve my social media engagement?") + conversation.add( + "user", + "Hello, how can I improve my social media engagement?" + ) response = conversation.complete() @@ -832,7 +1022,10 @@ def test_explicit_conversation_complete(emulate_app_process): @explicit def test_explicit_conversation_stream_complete(emulate_app_process): conversation = Conversation() - conversation.add("user", "Hello, how can I improve my social media engagement?") + conversation.add( + "user", + "Hello, how can I improve my social media engagement?" + ) response_chunks = [] for chunk in conversation.stream_complete(): @@ -894,6 +1087,7 @@ def test_function_three(number: int, coefficient: float): name="get_secret_word_by_password", parameters={ "password": { + "required": True, "type": "string", "description": "A password used to retrieve the secret word" } @@ -919,10 +1113,12 @@ def test_function_three(number: int, coefficient: float): name="calculate", parameters={ "number": { + "required": True, "type": "integer", - "description": "The base number to perform calculation against" + "description": "The base number to perform calculation against", }, "coefficient": { + "required": True, "type": "float", "description": "The coefficient to use against the number" } @@ -988,6 +1184,7 @@ def test_function_three(number: int, coefficient: float): name="get_secret_word_by_password", parameters={ "password": { + "required": True, "type": "string", "description": "A password used to retrieve the secret word" } @@ -1015,10 +1212,12 @@ def test_function_three(number: int, coefficient: float): name="calculate", parameters={ "number": { + "required": True, "type": "integer", "description": "The base number to perform calculation against" }, "coefficient": { + "required": True, "type": "float", "description": "The coefficient to use against the number" } @@ -1035,7 +1234,8 @@ def test_function_three(number: int, coefficient: float): @explicit @pytest.mark.asyncio async def test_explicit_complete(emulate_app_process): - initial_text = "Write a short paragraph about the benefits of regular exercise." + initial_text = "\ + Write a short paragraph about the benefits of regular exercise." response = complete(initial_text) assert isinstance(response, str) @@ -1046,7 +1246,8 @@ async def test_explicit_complete(emulate_app_process): @explicit @pytest.mark.asyncio async def test_explicit_stream_complete(emulate_app_process): - initial_text = "Write a short paragraph about the benefits of regular exercise." + initial_text = \ + "Write a short paragraph about the benefits of regular exercise." response_chunks = list(stream_complete(initial_text)) @@ -1058,7 +1259,10 @@ async def test_explicit_stream_complete(emulate_app_process): @explicit def test_explicit_create_graph(emulate_app_process, created_graphs): - graph = create_graph(name="integration_test_graph", description="Integration test graph") + graph = create_graph( + name="integration_test_graph", + description="Integration test graph" + ) created_graphs.append(graph) assert graph.id is not None assert graph.name == "integration_test_graph" @@ -1066,7 +1270,10 @@ def test_explicit_create_graph(emulate_app_process, created_graphs): @explicit def test_explicit_retrieve_graph(emulate_app_process, created_graphs): - created_graph = create_graph(name="integration_test_graph", description="Integration test graph") + created_graph = create_graph( + name="integration_test_graph", + description="Integration test graph" + ) created_graphs.append(created_graph) graph = retrieve_graph(graph_id=created_graph.id) @@ -1077,7 +1284,10 @@ def test_explicit_retrieve_graph(emulate_app_process, created_graphs): @explicit def test_explicit_list_graphs(emulate_app_process, created_graphs): # Create a graph to ensure there's at least one graph in the list - graph = create_graph(name="integration_test_graph", description="Integration test graph") + graph = create_graph( + name="integration_test_graph", + description="Integration test graph" + ) created_graphs.append(graph) graphs = list_graphs() @@ -1088,7 +1298,10 @@ def test_explicit_list_graphs(emulate_app_process, created_graphs): @explicit def test_explicit_delete_graph(emulate_app_process, created_graphs): - created_graph = create_graph(name="integration_test_graph", description="Integration test graph") + created_graph = create_graph( + name="integration_test_graph", + description="Integration test graph" + ) created_graphs.append(created_graph) response = delete_graph(graph_id_or_graph=created_graph.id) @@ -1100,8 +1313,11 @@ def test_explicit_delete_graph(emulate_app_process, created_graphs): @explicit def test_explicit_upload_file(emulate_app_process, created_files): - data = b"file_content" - file = upload_file(data=data, type="text/plain", name="integration_uploaded_file") + file = upload_file( + data=b"file_content", + type="text/plain", + name="integration_uploaded_file" + ) created_files.append(file) assert file.id is not None @@ -1110,7 +1326,11 @@ def test_explicit_upload_file(emulate_app_process, created_files): @explicit def test_explicit_retrieve_file(emulate_app_process, created_files): - uploaded_file = upload_file(data=b"file_content", type="text/plain", name="integration_uploaded_file") + uploaded_file = upload_file( + data=b"file_content", + type="text/plain", + name="integration_uploaded_file" + ) created_files.append(uploaded_file) file = retrieve_file(file_id=uploaded_file.id) @@ -1121,8 +1341,11 @@ def test_explicit_retrieve_file(emulate_app_process, created_files): @explicit def test_explicit_list_files(emulate_app_process, created_files): # Upload a file to ensure there's at least one file in the list - data = b"file_content" - file = upload_file(data=data, type="text/plain", name="integration_uploaded_file") + file = upload_file( + data=b"file_content", + type="text/plain", + name="integration_uploaded_file" + ) created_files.append(file) files = list_files() @@ -1133,7 +1356,11 @@ def test_explicit_list_files(emulate_app_process, created_files): @explicit def test_explicit_delete_file(emulate_app_process, created_files): - uploaded_file = upload_file(data=b"file_content", type="text/plain", name="integration_uploaded_file") + uploaded_file = upload_file( + data=b"file_content", + type="text/plain", + name="integration_uploaded_file" + ) created_files.append(uploaded_file) response = delete_file(file_id_or_file=uploaded_file.id) @@ -1182,7 +1409,7 @@ def test_explicit_ask_graph_class( ) assert isinstance(answer, str) - assert answer == "PARIS" + assert answer.strip() == "PARIS" @explicit @@ -1227,7 +1454,7 @@ def test_explicit_stream_ask_graph_class( answer += chunk assert isinstance(answer, str) - assert answer == " PARIS" + assert answer.strip() == "PARIS" @explicit @@ -1265,12 +1492,13 @@ def test_explicit_ask( break answer = ask( - question="What is the source word? Name only the word and nothing else", + question="What is the source word?" + + " Name only the word and nothing else", graphs_or_graph_ids=[graph] ) assert isinstance(answer, str) - assert answer == "PARIS" + assert answer.strip() == "PARIS" @explicit @@ -1309,14 +1537,196 @@ def test_explicit_stream_ask( answer = "" stream = stream_ask( - question="What is the source word? Name only the word and nothing else", + question="What is the source word?" + + " Name only the word and nothing else", graphs_or_graph_ids=[graph] ) for chunk in stream: answer += chunk assert isinstance(answer, str) - assert answer == " PARIS" + assert answer.strip() == "PARIS" + + +@explicit +def test_explicit_tools_parse_pdf(emulate_app_process, created_files): + pdf_file = upload_file( + data=test_pdf_data, + type="application/pdf", + name="uploaded_test.pdf" + ) + created_files.append(pdf_file) + + pdf_result = tools.parse_pdf(file_id_or_file=pdf_file.id) + assert pdf_result.strip() == "Hello PDF!" + + +@explicit +def test_explicit_tools_split_llm(emulate_app_process): + split_result = tools.split( + test_split_text + ) + assert isinstance(split_result, list) + assert len(split_result) > 1 + + +@explicit +def test_explicit_tools_split_fast(emulate_app_process): + split_result = tools.split( + test_fast_split_text, + strategy="fast_split" + ) + assert isinstance(split_result, list) + assert len(split_result) > 1 + + +@explicit +def test_explicit_tools_split_hybrid(emulate_app_process): + # Test using the hybrid_split strategy + split_result = tools.split( + test_split_text, + strategy="hybrid_split" + ) + assert isinstance(split_result, list) + assert len(split_result) > 1 + + +@explicit +def test_explicit_tools_comprehend_medical_entities(emulate_app_process): + medical_result = tools.comprehend_medical( + "The patient shows symptoms of mild hypertension." + ) + assert isinstance(medical_result, list) + assert len(medical_result) > 0 + + first_entity = medical_result[0] + assert hasattr(first_entity, "category") + assert first_entity.category == "MEDICAL_CONDITION" + assert hasattr(first_entity, "text") + assert first_entity.text.lower() == "hypertension" + assert hasattr(first_entity, "score") + assert first_entity.score > 0.5 # Ensure it's confident enough + + # The first entity's attributes should contain "mild" + assert hasattr(first_entity, "attributes") + assert len(first_entity.attributes) > 0 + first_attribute = first_entity.attributes[0] + assert hasattr(first_attribute, "text") + assert first_attribute.text.lower() == "mild" + + assert hasattr(first_entity, "traits") + assert len(first_entity.traits) > 0 + first_trait = first_entity.traits[0] + assert hasattr(first_trait, "name") + assert first_trait.name == "DIAGNOSIS" + + +@explicit +def test_explicit_tools_comprehend_medical_rxnorm(emulate_app_process): + # Test using RxNorm response type + medical_result = tools.comprehend_medical( + "The patient was prescribed Lisinopril for blood pressure management.", + response_type="RxNorm" + ) + + # Basic structure checks + assert isinstance(medical_result, list) + assert len(medical_result) > 0 + + first_entity = medical_result[0] + assert first_entity.category == "MEDICATION" + assert first_entity.text.lower() == "lisinopril" + + # Check that concepts are present and have expected structure + assert hasattr(first_entity, "concepts") + assert len(first_entity.concepts) > 0 + assert all( + hasattr(c, "code") + and + hasattr(c, "description") + and hasattr(c, "score") + for c in first_entity.concepts + ) + + # Verify that at least one concept matches the base drug name + assert any( + "lisinopril" in c.description.lower() + for c in first_entity.concepts + ) + + # Verify scores are reasonable + assert first_entity.score > 0.5 + for concept in first_entity.concepts: + assert concept.score > 0.5 + + +@explicit +def test_explicit_tools_comprehend_medical_icd10(emulate_app_process): + # Test using ICD-10-CM response type + medical_result = tools.comprehend_medical( + "The patient has been diagnosed with Type 2 diabetes mellitus.", + response_type="ICD-10-CM" + ) + assert isinstance(medical_result, list) + assert len(medical_result) > 0 + + first_entity = medical_result[0] + assert first_entity.category == "MEDICAL_CONDITION" + assert first_entity.text.lower() == "diabetes mellitus" + assert any( + "diabetes" in c.description.lower() + for c in first_entity.concepts + ) + + # Ensure there's a DIAGNOSIS trait + assert hasattr(first_entity, "traits") + assert len(first_entity.traits) > 0 + assert any(trait.name == "DIAGNOSIS" for trait in first_entity.traits) + + # Check ICD-10-CM concept codes + assert any(c.code.startswith("E11") for c in first_entity.concepts) + + +@explicit +def test_explicit_tools_comprehend_medical_snomed(emulate_app_process): + # Test using SNOMED CT response type + medical_result = tools.comprehend_medical( + "The patient presents with symptoms consistent with appendicitis.", + response_type="SNOMED CT" + ) + assert isinstance(medical_result, list) + assert len(medical_result) >= 2 + + symptoms_entity = medical_result[0] + appendicitis_entity = medical_result[1] + + # Check categories and text + assert symptoms_entity.category == "MEDICAL_CONDITION" + assert "symptoms" in symptoms_entity.text.lower() + + assert appendicitis_entity.category == "MEDICAL_CONDITION" + assert "appendicitis" in appendicitis_entity.text.lower() + + # Check traits + assert len(symptoms_entity.traits) > 0 + assert any(trait.name == "SYMPTOM" for trait in symptoms_entity.traits) + + assert len(appendicitis_entity.traits) > 0 + assert any( + trait.name == "DIAGNOSIS" + for trait in appendicitis_entity.traits + ) + + # Check that concepts have codes + assert len(symptoms_entity.concepts) > 0 + assert all(c.code and c.description for c in symptoms_entity.concepts) + + assert len(appendicitis_entity.concepts) > 0 + assert any( + "appendicitis" in c.description.lower() + for c in appendicitis_entity.concepts + ) + -# For doing a explicit test of apps.generate_content() we need a no-code app that -# nobody will touch. That is a challenge. +# For doing a explicit test of apps.generate_content() we need a no-code app +# that nobody will touch. That is a challenge. diff --git a/tests/e2e/package.json b/tests/e2e/package.json index c43465658..065b79fe2 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -15,12 +15,12 @@ "e2e:grep": "playwright test --project=chromium --grep " }, "dependencies": { - "writer-ui": "*", "express": "4.19.2", - "http-proxy": "1.18.1" + "http-proxy": "1.18.1", + "writer-ui": "*" }, "devDependencies": { - "@playwright/test": "1.42.1", + "@playwright/test": "^1.49.1", "nodemon": "3.1.0" } } diff --git a/tests/e2e/tests/builderFieldValidation.spec.ts b/tests/e2e/tests/builderFieldValidation.spec.ts new file mode 100644 index 000000000..dbbec0cae --- /dev/null +++ b/tests/e2e/tests/builderFieldValidation.spec.ts @@ -0,0 +1,86 @@ +import { test, expect } from "@playwright/test"; + +test.describe("Builder field validation", () => { + let url: string; + + test.beforeAll(async ({ request }) => { + const response = await request.post(`/preset/section`); + expect(response.ok()).toBeTruthy(); + ({ url } = await response.json()); + }); + + test.afterAll(async ({ request }) => { + await request.delete(url); + }); + + test.beforeEach(async ({ page }) => { + await page.goto(url, { waitUntil: "domcontentloaded" }); + test.setTimeout(5000); + }); + + test("should display error for invalid button fields", async ({ page }) => { + await page + .locator(`.BuilderSidebarToolkit [data-component-type="button"]`) + .dragTo(page.locator(".CoreSection")); + await page.locator(`button.CoreButton.component`).click(); + + // is disabled + + const isDisabledInput = page.locator( + '.BuilderFieldsText[data-automation-key="isDisabled"] input', + ); + + await isDisabledInput.fill("maybe"); + expect(await isDisabledInput.getAttribute("aria-invalid")).toBe("true"); + + await isDisabledInput.fill("yes"); + expect(await isDisabledInput.getAttribute("aria-invalid")).toBe("false"); + + // css classes + + const cssClasses = page.locator( + '.BuilderFieldsText[data-automation-key="cssClasses"] input', + ); + await cssClasses.fill("1234"); + expect(await cssClasses.getAttribute("aria-invalid")).toBe("true"); + + await cssClasses.fill("class1 class2"); + expect(await cssClasses.getAttribute("aria-invalid")).toBe("false"); + }); + + test("should display error for invalid multiselectinput fields", async ({ + page, + }) => { + await page + .locator( + `.BuilderSidebarToolkit [data-component-type="multiselectinput"]`, + ) + .dragTo(page.locator(".CoreSection")); + await page.locator(`.CoreMultiselectInput.component`).click(); + + // maximum count + + const maximunCountInput = page.locator( + '.BuilderFieldsText[data-automation-key="maximumCount"] input', + ); + + await maximunCountInput.fill("-1"); + expect(await maximunCountInput.getAttribute("aria-invalid")).toBe("true"); + + await maximunCountInput.fill("2"); + expect(await maximunCountInput.getAttribute("aria-invalid")).toBe("false"); + + // options + + await page.locator(".BuilderFieldsOptions button").nth(1).click(); + + const optionsTextarea = page.locator( + '.BuilderFieldsObject[data-automation-key="options"] textarea', + ); + await optionsTextarea.fill(JSON.stringify(true)); + expect(await optionsTextarea.getAttribute("aria-invalid")).toBe("true"); + + await optionsTextarea.fill(JSON.stringify({ a: "A", b: "B" })); + expect(await optionsTextarea.getAttribute("aria-invalid")).toBe("false"); + }); +}); diff --git a/tests/e2e/tests/stateAutocompletion.spec.ts b/tests/e2e/tests/stateAutocompletion.spec.ts index 5deab244e..666ceef6c 100644 --- a/tests/e2e/tests/stateAutocompletion.spec.ts +++ b/tests/e2e/tests/stateAutocompletion.spec.ts @@ -137,8 +137,8 @@ test.describe("state autocompletion", () => { } testFieldType("BuilderFieldsColor", "primaryTextColor", 'div.CoreText.component'); - testFieldType("BuilderFieldsShadow", "buttonShadow", '.BuilderSidebarComponentTreeBranch [data-automation-key="root"]'); - testFieldType("BuilderFieldsAlign", "contentHAlign", '.BuilderSidebarComponentTreeBranch [data-automation-key="root"]'); - testFieldType("BuilderFieldsPadding", "contentPadding", '.BuilderSidebarComponentTreeBranch [data-automation-key="root"]'); - testFieldType("BuilderFieldsWidth", "contentWidth", '.BuilderSidebarComponentTreeBranch [data-automation-key="root"]'); + testFieldType("BuilderFieldsShadow", "buttonShadow", '.BuilderSidebarComponentTree [data-automation-key="root"]'); + testFieldType("BuilderFieldsAlign", "contentHAlign", '.BuilderSidebarComponentTree [data-automation-key="root"]'); + testFieldType("BuilderFieldsPadding", "contentPadding", '.BuilderSidebarComponentTree [data-automation-key="root"]'); + testFieldType("BuilderFieldsWidth", "contentWidth", '.BuilderSidebarComponentTree [data-automation-key="root"]'); }); diff --git a/tests/e2e/tests/workflows.spec.ts b/tests/e2e/tests/workflows.spec.ts index 8854efd19..e70cc746a 100644 --- a/tests/e2e/tests/workflows.spec.ts +++ b/tests/e2e/tests/workflows.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from "@playwright/test"; +import { test, expect, Locator } from "@playwright/test"; test.describe("Workflows", () => { let url: string; @@ -14,7 +14,7 @@ test.describe("Workflows", () => { }); test.beforeEach(async ({ page }) => { - await page.goto(url, {waitUntil: "domcontentloaded"}); + await page.goto(url, { waitUntil: "domcontentloaded" }); }); const inputData = [ @@ -27,9 +27,12 @@ test.describe("Workflows", () => { page, }) => { await page.getByPlaceholder(object).fill(color); - await page.locator(`[data-automation-action="toggle-panel"][data-automation-key="log"]`).click(); - const rowsLocator = page - .locator(".BuilderPanelSwitcher div.row"); + await page + .locator( + `[data-automation-action="toggle-panel"][data-automation-key="log"]`, + ) + .click(); + const rowsLocator = page.locator(".BuilderPanelSwitcher div.row"); await expect(rowsLocator).toHaveCount(3); const rowLocator = rowsLocator.filter({ hasText: "Return value" }); await rowLocator.getByRole("button", { name: "Details" }).click(); @@ -61,7 +64,9 @@ test.describe("Workflows", () => { .dragTo(page.locator(".WorkflowsWorkflow"), { targetPosition: { x: 100, y: 100 }, }); - const runWorkflowBlock = page.locator(`.WorkflowsNode.wf-type-workflows_runworkflow`); + const runWorkflowBlock = page.locator( + `.WorkflowsNode.wf-type-workflows_runworkflow`, + ); await page .locator( @@ -70,25 +75,37 @@ test.describe("Workflows", () => { .dragTo(page.locator(".WorkflowsWorkflow"), { targetPosition: { x: 400, y: 100 }, }); - const returnValueBlock = page.locator(`.WorkflowsNode.wf-type-workflows_returnvalue`); + const returnValueBlock = page.locator( + `.WorkflowsNode.wf-type-workflows_returnvalue`, + ); await runWorkflowBlock.click(); - await page.locator(`.BuilderFieldsText[data-automation-key="workflowKey"] input`).fill("repeat_payload"); + await page + .locator(`.BuilderFieldsText[data-automation-key="workflowKey"] input`) + .fill("repeat_payload"); const payload = "blue"; - await page.locator(`.BuilderFieldsText[data-automation-key="payload"] textarea`).fill(payload); + await page + .locator(`.BuilderFieldsText[data-automation-key="payload"] textarea`) + .fill(payload); await page.locator(`[data-automation-action="collapse-settings"]`).click(); await runWorkflowBlock.locator(".ball.success").dragTo(returnValueBlock); await returnValueBlock.click(); - await page.locator(`.BuilderFieldsText[data-automation-key="value"] textarea`).fill("@{result}"); + await page + .locator(`.BuilderFieldsText[data-automation-key="value"] textarea`) + .fill("@{result}"); await page.locator(`[data-automation-action="run-workflow"]`).click(); - await page.locator(`[data-automation-action="toggle-panel"][data-automation-key="log"]`).click(); + await page + .locator( + `[data-automation-action="toggle-panel"][data-automation-key="log"]`, + ) + .click(); const rowsLocator = page.locator(".BuilderPanelSwitcher div.row"); await expect(rowsLocator).toHaveCount(3); - const rowLocator = rowsLocator.filter({ hasText: "Return value" }).first();; + const rowLocator = rowsLocator.filter({ hasText: "Return value" }).first(); await rowLocator.getByRole("button", { name: "Details" }).click(); await expect(page.locator(".BuilderModal")).toBeVisible(); const returnValueLocator = page.locator( @@ -96,4 +113,86 @@ test.describe("Workflows", () => { ); await expect(returnValueLocator).toContainText("blue"); }); -}); \ No newline at end of file + + test.describe("multiple selection", () => { + let runWorkflowBlock: Locator; + let returnValueBlock: Locator; + + test.beforeEach(async ({ page }) => { + await page + .locator(`[data-automation-action="set-mode-workflows"]`) + .click(); + await page.locator(`[data-automation-action="add-workflow"]`).click(); + + await page + .locator( + `.BuilderSidebarToolkit [data-component-type="workflows_runworkflow"]`, + ) + .dragTo(page.locator(".WorkflowsWorkflow"), { + targetPosition: { x: 100, y: 100 }, + }); + runWorkflowBlock = page.locator( + `.WorkflowsNode.wf-type-workflows_runworkflow`, + ); + + await page + .locator( + `.BuilderSidebarToolkit [data-component-type="workflows_returnvalue"]`, + ) + .dragTo(page.locator(".WorkflowsWorkflow"), { + targetPosition: { x: 400, y: 100 }, + }); + returnValueBlock = page.locator( + `.WorkflowsNode.wf-type-workflows_returnvalue`, + ); + + await expect(page.locator(`.WorkflowsNode`)).toHaveCount(2); + + await runWorkflowBlock.click(); + await returnValueBlock.click({ modifiers: ["Shift"] }); + + await expect(page.locator(`.WorkflowsNode.selected`)).toHaveCount(2); + }); + + test("clear selection", async ({ page }) => { + await page + .locator( + '.BuilderSettingsActions [data-automation-action="clear-selection"]', + ) + .click(); + + await expect(page.locator(`.WorkflowsNode.selected`)).toHaveCount(0); + }); + + test("remove multiple elements", async ({ page }) => { + await page + .locator('.BuilderSettingsActions [data-automation-action="delete"]') + .click(); + + await expect(page.locator(`.WorkflowsNode`)).toHaveCount(0); + }); + + test("drag multiple elements", async ({ page }) => { + const returnValueBlockBoundingBefore = + await returnValueBlock.boundingBox(); + + await runWorkflowBlock.click(); + await returnValueBlock.click({ modifiers: ["Shift"] }); + + await expect(page.locator(`.WorkflowsNode.selected`)).toHaveCount(2); + + await runWorkflowBlock.dragTo(page.locator(".WorkflowsWorkflow"), { + targetPosition: { x: 110, y: 110 }, + }); + + const returnValueBlockBounding = await returnValueBlock.boundingBox(); + + expect(returnValueBlockBounding?.x).not.toBe( + returnValueBlockBoundingBefore?.x, + ); + expect(returnValueBlockBounding?.y).not.toBe( + returnValueBlockBoundingBefore?.y, + ); + }); + }); +});