diff --git a/.fernignore b/.fernignore index 48e2efe29..e4b381d5b 100644 --- a/.fernignore +++ b/.fernignore @@ -11,6 +11,7 @@ src/label_studio_sdk/label_interface src/label_studio_sdk/client.py src/label_studio_sdk/tasks/client_ext.py src/label_studio_sdk/projects/client_ext.py +src/label_studio_sdk/projects/exports/client_ext.py # converter src/label_studio_sdk/converter diff --git a/.github/workflows/fm-command.yml b/.github/workflows/fm-command.yml deleted file mode 100644 index 6d8ceb4e0..000000000 --- a/.github/workflows/fm-command.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: "/fm command" - -on: - repository_dispatch: - types: [ fm-command ] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.client_payload.github.payload.issue.number }}-${{ github.event.client_payload.slash_command.command }}-${{ github.event.client_payload.slash_command.args.unnamed.arg1 || github.event.client_payload.slash_command.args.all }} - -jobs: - sync: - name: "Update: Update Feature Flags" - if: github.event.client_payload.slash_command.args.unnamed.arg1 == 'sync' - uses: ./.github/workflows/follow-merge-upstream-repo-sync-v2.yml - with: - branch_name: ${{ github.event.client_payload.pull_request.head.ref }} - secrets: inherit - - help: - if: ${{ github.event.client_payload.slash_command.args.unnamed.arg1 == 'help' || !contains(fromJson('["sync"]'), github.event.client_payload.slash_command.args.unnamed.arg1) }} - runs-on: ubuntu-latest - timeout-minutes: 1 - steps: - - name: Update comment - uses: peter-evans/create-or-update-comment@v4 - with: - token: ${{ secrets.GIT_PAT }} - repository: ${{ github.event.client_payload.github.payload.repository.full_name }} - comment-id: ${{ github.event.client_payload.github.payload.comment.id }} - body: | - > Command | Description - > --- | --- - > /fm sync | Sync upstream prs and merge with pull request base - reaction-type: hooray diff --git a/.github/workflows/follow-merge-upstream-repo-sync-v2.yml b/.github/workflows/follow-merge-upstream-repo-sync-v2.yml deleted file mode 100644 index d2622419a..000000000 --- a/.github/workflows/follow-merge-upstream-repo-sync-v2.yml +++ /dev/null @@ -1,277 +0,0 @@ -name: 'Follow Merge: Upstream repo sync v2' - -on: - workflow_call: - inputs: - branch_name: - required: true - type: string - workflow_dispatch: - inputs: - branch_name: - description: 'Branch name' - required: true - type: string - -concurrency: - group: ${{ github.workflow }}-${{ inputs.branch_name }} - cancel-in-progress: true - -env: - NODE: "18" - YARN: "1.22" - -jobs: - sync: - name: Sync PR - runs-on: ubuntu-latest - outputs: - adala: "${{ steps.upstream-prs.outputs.adala }}" - label-studio-query-vectordb: "${{ steps.upstream-prs.outputs.label-studio-query-vectordb }}" - steps: - - uses: hmarr/debug-action@v3.0.0 - - - name: Add Workflow link to chat ops command comment - if: github.event.client_payload.github.payload.comment.id && github.event.client_payload.github.payload.repository.full_name - uses: peter-evans/create-or-update-comment@v4 - with: - token: ${{ secrets.GIT_PAT }} - repository: ${{ github.event.client_payload.github.payload.repository.full_name }} - comment-id: ${{ github.event.client_payload.github.payload.comment.id }} - body: | - > [Workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) - - - name: Checkout Actions Hub - uses: actions/checkout@v4 - with: - token: ${{ secrets.GIT_PAT }} - repository: HumanSignal/actions-hub - path: ./.github/actions-hub - - - name: Find or Create branch - uses: ./.github/actions-hub/actions/github-find-or-create-branch - id: get-branch - with: - github_token: ${{ secrets.GIT_PAT }} - branch_name: "${{ inputs.branch_name }}" - - - name: Checkout label-studio-client-generator - uses: actions/checkout@v4 - with: - token: ${{ secrets.GIT_PAT }} - repository: HumanSignal/label-studio-client-generator - ref: ${{ steps.get-branch.outputs.branch_name }} - fetch-depth: 0 - - - name: Checkout Actions Hub - uses: actions/checkout@v4 - with: - token: ${{ secrets.GIT_PAT }} - repository: HumanSignal/actions-hub - path: ./.github/actions-hub - - - name: Get Upstream PRs - id: upstream-prs - uses: actions/github-script@v7 - env: - BRANCH_NAME: ${{ inputs.branch_name }} - with: - github-token: ${{ secrets.GIT_PAT }} - script: | - const { repo, owner } = context.repo; - const branch_name = process.env.BRANCH_NAME; - const pyProjectPath = "pyproject.toml"; - const repos = ["label-studio-client-generator"]; - const repos_infra = []; - const base_branch_name = context.repo.default_branch; - - let shas = {}; - - // GET UPSTREAM PRS - let upstream_pulls = []; - for (const repo of repos.concat(repos_infra)) { - const {data: pulls} = await github.rest.pulls.list({ - owner, - repo, - state: "all", - head: `${owner}:${branch_name}`, - }); - const first_open_pull = pulls.find(e => e.state === 'open'); - const pull = first_open_pull || pulls[0]; - if (pull) { - core.info(`PRs found for ${repo} ${pull.html_url} ${pull.merged_at ? 'merged' : pull.state} ${pull.merged_at ? pull.merge_commit_sha : pull.head.sha}`) - upstream_pulls.push(pull); - if (pull.merged_at) { - shas[repo] = pull.merge_commit_sha; - } else if (pull.state === 'open') { - shas[repo] = pull.head.sha; - } - } else { - core.notice(`No open upstream PRs found for ${repo}`) - } - } - - for (const [key, value] of Object.entries(shas)) { - core.setOutput(key, value); - } - - core.info(`Base branch name ${base_branch_name}`); - core.setOutput('base_branch_name', base_branch_name); - - if (upstream_pulls.length > 0) { - core.info(`Title ${upstream_pulls[0].title}`); - core.setOutput('title', upstream_pulls[0].title); - } - - const upstream_prs_urls = upstream_pulls.map(e => e.html_url).join(','); - core.info(`Upstream PRs URLs ${upstream_prs_urls}`); - core.setOutput('upstream_prs_urls', upstream_prs_urls); - - let assignees = []; - for (const pull of upstream_pulls) { - if (pull.user) assignees.push(pull.user.login); - if (pull.assignee) assignees.push(pull.assignee.login); - assignees.concat(pull.assignees.map(e => e.name)); - } - assignees = assignees.filter(x => x !== 'robot-ci-heartex'); - core.info(`Assignees ${assignees.join(',')}`); - core.setOutput('assignees', assignees.join(',')); - - if (assignees.length > 0) { - const author_username = assignees[0]; - core.info(`Author username ${author_username}`); - core.setOutput('author_username', author_username); - } - - let status = "open" - if (upstream_pulls.every(p => p.merged_at)) { - status = 'merged'; - } else if (upstream_pulls.every(p => p.closed_at)) { - status = 'closed'; - } - core.info(`Status: ${status}`); - core.setOutput("status", status); - - - name: Git Configure - uses: ./.github/actions-hub/actions/git-configure - with: - username: ${{ steps.upstream-prs.outputs.author_username }} - - - name: Setup node - uses: actions/setup-node@v4 - - - name: Install Fern - run: npm install -g fern-api@latest - - - name: Download Fern - run: npm install -g fern-api@latest - - - name: Set Fern Generator Branch and Mode - env: - BRANCH_NAME: "${{ steps.get-branch.outputs.branch_name }}" - FERN_GENERATOR_PATH: "fern/generators.yml" - run: | - yq e --inplace ".groups.python-sdk-staging.generators[0].github.branch |= \"${BRANCH_NAME}\"" "${FERN_GENERATOR_PATH}" - yq e --inplace ".groups.python-sdk-staging.generators[0].github.mode |= \"push\"" "${FERN_GENERATOR_PATH}" - cat "${FERN_GENERATOR_PATH}" - - - name: Check Fern API is valid - run: fern check - - - name: Generate Python SDK - env: - FERN_TOKEN: ${{ secrets.FERN_TOKEN }} - working-directory: "${{ env.UPSTREAM_REPO_WORKDIR }}" - run: fern generate --group python-sdk-staging --log-level debug - - - name: Find or Create PR - uses: ./.github/actions-hub/actions/follow-merge-find-or-create-pull-request - id: get-pr - with: - github_token: ${{ secrets.GIT_PAT }} - branch_name: "${{ steps.get-branch.outputs.branch_name }}" - title: "${{ steps.upstream-prs.outputs.title }}" - upstream_prs_urls: "${{ steps.upstream-prs.outputs.upstream_prs_urls }}" - - - name: Add PR Assignees - if: steps.upstream-prs.outputs.assignees - uses: ./.github/actions-hub/actions/github-add-pull-request-assignees - continue-on-error: true - with: - github_token: ${{ secrets.GIT_PAT }} - pullrequest_number: "${{ steps.get-pr.outputs.number }}" - assignees: "${{ steps.upstream-prs.outputs.assignees }}" - - - name: Convert to ready for review - if: steps.upstream-prs.outputs.status == 'merged' - id: ready-for-review-pr - shell: bash - env: - GIT_PAT: ${{ secrets.GIT_PAT }} - run: | - echo "$GIT_PAT" | gh auth login --with-token - gh api graphql -F id='${{ steps.get-pr.outputs.node_id }}' -f query=' - mutation($id: ID!) { - markPullRequestReadyForReview(input: { pullRequestId: $id }) { - pullRequest { - id - } - } - } - ' - - - name: Enable AutoMerge - if: steps.upstream-prs.outputs.status == 'merged' - continue-on-error: true - shell: bash - env: - GIT_PAT: ${{ secrets.GIT_PAT }} - run: | - echo "$GIT_PAT" | gh auth login --with-token - gh api graphql -f pull='${{ steps.get-pr.outputs.node_id }}' -f query=' - mutation($pull: ID!) { - enablePullRequestAutoMerge(input: {pullRequestId: $pull, mergeMethod: SQUASH}) { - pullRequest { - id - number - } - } - }' - - - name: Close PR - if: steps.upstream-prs.outputs.status == 'closed' - continue-on-error: true - shell: bash - env: - GIT_PAT: ${{ secrets.GIT_PAT }} - run: | - echo "$GIT_PAT" | gh auth login --with-token - gh api graphql -f pull='${{ steps.get-pr.outputs.node_id }}' -f query=' - mutation($pull: ID!) { - closePullRequest(input: {pullRequestId: $pull }) { - pullRequest { - id - state - } - } - }' - - # - name: Notify on failure - # uses: ./.github/actions-hub/actions/github-create-comment - # if: failure() - # with: - # github_token: ${{ secrets.GIT_PAT }} - # repository: "${{ steps.fm.outputs.repo_name }}" - # issue_number: "${{ steps.fm.outputs.pr_number }}" - # body: | - # Follow Merge downstream workflow has been failed. - # > [Workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) - - - name: Add reaction to chat ops command comment - if: always() && github.event.client_payload.github.payload.comment.id && github.event.client_payload.github.payload.repository.full_name - uses: peter-evans/create-or-update-comment@v4 - with: - token: ${{ secrets.GIT_PAT }} - repository: ${{ github.event.client_payload.github.payload.repository.full_name }} - comment-id: ${{ github.event.client_payload.github.payload.comment.id }} - reactions: ${{ job.status == 'success' && '+1' || '-1' }} diff --git a/.mock/definition/__package__.yml b/.mock/definition/__package__.yml index c581df299..0be47f3f1 100644 --- a/.mock/definition/__package__.yml +++ b/.mock/definition/__package__.yml @@ -741,115 +741,6 @@ types: converted_formats: optional> source: openapi: openapi/openapi.yaml - TaskFilterOptions: - properties: - view: - type: optional - docs: Apply filters from the view ID (a tab from the Data Manager) - skipped: - type: optional - docs: >- - `only` - include all tasks with skipped annotations
`exclude` - - exclude all tasks with skipped annotations - finished: - type: optional - docs: >- - `only` - include all finished tasks (is_labeled = true)
`exclude` - - exclude all finished tasks - annotated: - type: optional - docs: >- - `only` - include all tasks with at least one not skipped - annotation
`exclude` - exclude all tasks with at least one not - skipped annotation - only_with_annotations: - type: optional - default: false - source: - openapi: openapi/openapi.yaml - AnnotationFilterOptions: - properties: - usual: - type: optional - docs: Include not skipped and not ground truth annotations - default: true - ground_truth: - type: optional - docs: Include ground truth annotations - skipped: - type: optional - docs: Include skipped annotations - source: - openapi: openapi/openapi.yaml - SerializationOption: - docs: JSON dict with parameters - properties: - only_id: - type: optional - docs: Include a full json body or IDs only - default: false - source: - openapi: openapi/openapi.yaml - SerializationOptions: - properties: - drafts: optional - predictions: optional - include_annotation_history: - type: optional - docs: Include annotation history - default: false - annotations__completed_by: optional - interpolate_key_frames: - type: optional - docs: Interpolate video key frames - default: false - source: - openapi: openapi/openapi.yaml - ExportCreateStatus: - enum: - - created - - in_progress - - failed - - completed - inline: true - source: - openapi: openapi/openapi.yaml - ExportCreate: - properties: - title: - type: optional - validation: - maxLength: 2048 - id: optional - created_by: optional - created_at: - type: optional - docs: Creation time - finished_at: - type: optional - docs: Complete or fail time - status: optional - md5: - type: optional - validation: - minLength: 1 - maxLength: 128 - counters: optional> - converted_formats: optional> - task_filter_options: optional - annotation_filter_options: optional - serialization_options: optional - source: - openapi: openapi/openapi.yaml - ExportConvert: - properties: - export_type: - type: string - docs: Export file format. - validation: - minLength: 1 - source: - openapi: openapi/openapi.yaml ProjectImportStatus: enum: - created @@ -2330,6 +2221,38 @@ types: created_at: optional source: openapi: openapi/openapi.yaml + InteractionData: + properties: + index: optional + action: optional + type: optional + status: optional + additional_data: optional> + source: + openapi: openapi/openapi.yaml + ProductTourState: + enum: + - ready + - completed + - skipped + inline: true + source: + openapi: openapi/openapi.yaml + ProductTour: + properties: + id: optional + user: optional + name: optional + state: optional + interaction_data: optional + created_at: optional + updated_at: optional + steps: + type: optional>> + docs: Tour steps + source: + openapi: openapi/openapi.yaml + ApiUsersProductTourUpdate: unknown PromptCreatedBy: discriminated: false docs: User ID of the creator of the prompt @@ -2716,3 +2639,124 @@ types: resolved_at: optional source: openapi: openapi/openapi.yaml + ExportFormat: + enum: + - JSON + - JSON_MIN + - CSV + - TSV + - CONLL2003 + - COCO + - VOC + - BRUSH_TO_NUMPY + - BRUSH_TO_PNG + - ASR_MANIFEST + - YOLO + - YOLO_OBB + - CSV_OLD + - YOLO_WITH_IMAGES + - COCO_WITH_IMAGES + - YOLO_OBB_WITH_IMAGES + docs: Export file format. + source: + openapi: openapi/openapi.yaml + TaskFilterOptions: + properties: + view: + type: optional + docs: Apply filters from the view ID (a tab from the Data Manager) + skipped: + type: optional + docs: >- + `only` - include all tasks with skipped annotations
`exclude` - + exclude all tasks with skipped annotations + finished: + type: optional + docs: >- + `only` - include all finished tasks (is_labeled = true)
`exclude` - + exclude all finished tasks + annotated: + type: optional + docs: >- + `only` - include all tasks with at least one not skipped + annotation
`exclude` - exclude all tasks with at least one not + skipped annotation + only_with_annotations: + type: optional + default: false + source: + openapi: openapi/openapi.yaml + AnnotationFilterOptions: + properties: + usual: + type: optional + docs: Include not skipped and not ground truth annotations + default: true + ground_truth: + type: optional + docs: Include ground truth annotations + skipped: + type: optional + docs: Include skipped annotations + source: + openapi: openapi/openapi.yaml + SerializationOption: + docs: JSON dict with parameters + properties: + only_id: + type: optional + docs: Include a full json body or IDs only + default: false + source: + openapi: openapi/openapi.yaml + SerializationOptions: + properties: + drafts: optional + predictions: optional + include_annotation_history: + type: optional + docs: Include annotation history + default: false + annotations__completed_by: optional + interpolate_key_frames: + type: optional + docs: Interpolate video key frames + default: false + source: + openapi: openapi/openapi.yaml + ExportSnapshotStatus: + enum: + - created + - in_progress + - failed + - completed + inline: true + source: + openapi: openapi/openapi.yaml + ExportSnapshot: + properties: + title: + type: optional + validation: + maxLength: 2048 + id: optional + created_by: optional + created_at: + type: optional + docs: Creation time + finished_at: + type: optional + docs: Complete or fail time + status: optional + md5: + type: optional + validation: + minLength: 1 + maxLength: 128 + counters: optional> + converted_formats: optional> + task_filter_options: optional + annotation_filter_options: optional + serialization_options: optional + source: + openapi: openapi/openapi.yaml diff --git a/.mock/definition/projects/exports.yml b/.mock/definition/projects/exports.yml index 971a063ba..a2c3a588f 100644 --- a/.mock/definition/projects/exports.yml +++ b/.mock/definition/projects/exports.yml @@ -2,7 +2,7 @@ service: auth: false base-path: '' endpoints: - create_export: + download_sync: path: /api/projects/{id}/export method: GET auth: true @@ -60,7 +60,7 @@ service: docs: A unique integer value identifying this project. display-name: Easy export of tasks and annotations request: - name: ExportsCreateExportRequest + name: ExportsDownloadSyncRequest query-parameters: export_type: type: optional @@ -85,7 +85,7 @@ service: Specify a list of task IDs to retrieve only the details for those tasks. response: - docs: Exported data + docs: Exported data in binary format type: file audiences: - public @@ -113,17 +113,23 @@ service: display-name: Get export formats response: docs: Export formats - type: list + type: list examples: - path-parameters: id: 1 response: body: - - string + - name: JSON + title: title + description: description + link: link + tags: + - tags + disabled: true audiences: - public list: - path: /api/projects/{id}/exports/ + path: /api/projects/{project_id}/exports method: GET auth: true docs: > @@ -139,7 +145,7 @@ service: source: openapi: openapi/openapi.yaml path-parameters: - id: + project_id: type: integer docs: A unique integer value identifying this project. display-name: List all export snapshots @@ -148,7 +154,7 @@ service: type: list examples: - path-parameters: - id: 1 + project_id: 1 response: body: - title: title @@ -170,7 +176,7 @@ service: audiences: - public create: - path: /api/projects/{id}/exports/ + path: /api/projects/{project_id}/exports method: POST auth: true docs: > @@ -194,19 +200,19 @@ service: source: openapi: openapi/openapi.yaml path-parameters: - id: + project_id: type: integer docs: A unique integer value identifying this project. display-name: Create new export snapshot request: - body: root.ExportCreate + body: root.ExportSnapshot content-type: application/json response: docs: '' - type: root.ExportCreate + type: root.ExportSnapshot examples: - path-parameters: - id: 1 + project_id: 1 request: {} response: body: @@ -250,8 +256,51 @@ service: interpolate_key_frames: true audiences: - public + download: + path: /api/projects/{project_id}/exports/{export_pk}/download + method: GET + auth: true + docs: > + + Download an export snapshot as a file in a specified format. To see what + formats are supported, you can use [Get export formats](list-formats) or + see [Export formats supported by Label + Studio](https://labelstud.io/guide/export#Export-formats-supported-by-Label-Studio). + + + You will need to provide the project ID and export ID (`export_pk`). The + export ID is returned when you create the export or you can use [List + all export snapshots](list). + + + The project ID can be found in the URL when viewing the project in Label + Studio, or you can retrieve all project IDs using [List all + projects](../list). + source: + openapi: openapi/openapi.yaml + path-parameters: + project_id: + type: integer + docs: A unique integer value identifying this project. + export_pk: + type: string + docs: Primary key identifying the export file. + display-name: Download export snapshot + request: + name: ExportsDownloadRequest + query-parameters: + exportType: + type: optional + docs: >- + Selected export format. JSON is available by default. For other + formats, you need to convert the export first. + response: + docs: Exported data in binary format + type: file + audiences: + - public get: - path: /api/projects/{id}/exports/{export_pk} + path: /api/projects/{project_id}/exports/{export_pk} method: GET auth: true docs: > @@ -270,7 +319,7 @@ service: source: openapi: openapi/openapi.yaml path-parameters: - id: + project_id: type: integer docs: A unique integer value identifying this project. export_pk: @@ -282,7 +331,7 @@ service: type: root.Export examples: - path-parameters: - id: 1 + project_id: 1 export_pk: export_pk response: body: @@ -308,7 +357,7 @@ service: audiences: - public delete: - path: /api/projects/{id}/exports/{export_pk} + path: /api/projects/{project_id}/exports/{export_pk} method: DELETE auth: true docs: > @@ -322,7 +371,7 @@ service: source: openapi: openapi/openapi.yaml path-parameters: - id: + project_id: type: integer docs: A unique integer value identifying this project. export_pk: @@ -331,12 +380,12 @@ service: display-name: Delete export snapshot examples: - path-parameters: - id: 1 + project_id: 1 export_pk: export_pk audiences: - public convert: - path: /api/projects/{id}/exports/{export_pk}/convert + path: /api/projects/{project_id}/exports/{export_pk}/convert method: POST auth: true docs: > @@ -361,7 +410,7 @@ service: source: openapi: openapi/openapi.yaml path-parameters: - id: + project_id: type: integer docs: A unique integer value identifying this project. export_pk: @@ -369,65 +418,62 @@ service: docs: Primary key identifying the export file. display-name: Export conversion request: - body: root.ExportConvert + name: ExportsConvertRequest + body: + properties: + export_type: optional + download_resources: + type: optional + docs: >- + If true, download all resource files such as images, audio, and + others relevant to the tasks. content-type: application/json response: docs: '' - type: root.ExportConvert + type: ExportsConvertResponse examples: - path-parameters: - id: 1 + project_id: 1 export_pk: export_pk - request: - export_type: export_type + request: {} response: body: - export_type: export_type - audiences: - - public - download: - path: /api/projects/{id}/exports/{export_pk}/download - method: GET - auth: true - docs: > - - Download an export snapshot as a file in a specified format. To see what - formats are supported, you can use [Get export formats](list-formats) or - see [Export formats supported by Label - Studio](https://labelstud.io/guide/export#Export-formats-supported-by-Label-Studio). - - - You will need to provide the project ID and export ID (`export_pk`). The - export ID is returned when you create the export or you can use [List - all export snapshots](list). - - - The project ID can be found in the URL when viewing the project in Label - Studio, or you can retrieve all project IDs using [List all - projects](../list). - source: - openapi: openapi/openapi.yaml - path-parameters: - id: - type: integer - docs: A unique integer value identifying this project. - export_pk: - type: string - docs: Primary key identifying the export file. - display-name: Download export snapshot - request: - name: ExportsDownloadRequest - query-parameters: - exportType: - type: optional - docs: Selected export format - examples: - - path-parameters: - id: 1 - export_pk: export_pk + export_type: JSON + converted_format: 1 audiences: - public source: openapi: openapi/openapi.yaml imports: root: ../__package__.yml +types: + ExportsListFormatsResponseItem: + properties: + name: optional + title: + type: optional + docs: Export format title + description: + type: optional + docs: Export format description + link: + type: optional + docs: Export format documentation link + validation: + format: uri + tags: + type: optional> + docs: Export format tags + disabled: + type: optional + docs: If true, the export format is not supported by the project. + source: + openapi: openapi/openapi.yaml + ExportsConvertResponse: + properties: + export_type: optional + converted_format: + type: optional + docs: ID of the converted format + source: + openapi: openapi/openapi.yaml diff --git a/.mock/definition/users.yml b/.mock/definition/users.yml index 3c0199e85..b89f4cbf5 100644 --- a/.mock/definition/users.yml +++ b/.mock/definition/users.yml @@ -328,10 +328,41 @@ service: path: /api/current-user/product-tour method: GET auth: true + docs: >- + Retrieve the state of a product tour for the current user. Creates a new + tour if it doesn't exist. source: openapi: openapi/openapi.yaml + display-name: Retrieve product tour state + request: + name: UsersGetProductTourRequest + query-parameters: + name: + type: string + docs: Name of the product tour to retrieve. + response: + docs: Successfully retrieved product tour + type: root.ProductTour examples: - - {} + - query-parameters: + name: name + response: + body: + id: 1 + user: 1 + name: name + state: ready + interaction_data: + index: 1 + action: action + type: type + status: status + additional_data: + key: value + created_at: '2024-01-15T09:30:00Z' + updated_at: '2024-01-15T09:30:00Z' + steps: + - key: value audiences: - internal update_product_tour: @@ -340,8 +371,18 @@ service: auth: true source: openapi: openapi/openapi.yaml - examples: - - {} + display-name: Update product tour state + request: + body: root.ApiUsersProductTourUpdate + query-parameters: + name: + type: string + docs: Name of the product tour to update + name: UsersUpdateProductTourRequest + content-type: application/json + response: + docs: Successfully updated product tour + type: root.ProductTour audiences: - internal source: diff --git a/.mock/definition/versions.yml b/.mock/definition/versions.yml new file mode 100644 index 000000000..2f052dcb5 --- /dev/null +++ b/.mock/definition/versions.yml @@ -0,0 +1,78 @@ +types: + VersionsGetResponseEdition: + enum: + - Community + - Enterprise + docs: Label Studio edition (Community or Enterprise) + inline: true + source: + openapi: openapi/openapi.yaml + VersionsGetResponse: + properties: + release: + type: optional + docs: Current release version of Label Studio + label-studio-os-package: + type: optional> + docs: Information about the Label Studio open source package + label-studio-os-backend: + type: optional> + docs: Information about the Label Studio backend + label-studio-frontend: + type: optional> + docs: Information about the Label Studio frontend + dm2: + type: optional> + docs: Information about the Data Manager 2.0 component + label-studio-converter: + type: optional> + docs: Information about the Label Studio converter component + edition: + type: optional + docs: Label Studio edition (Community or Enterprise) + lsf: + type: optional> + docs: Information about the Label Studio Frontend library + backend: + type: optional> + docs: Additional backend information + source: + openapi: openapi/openapi.yaml +service: + auth: false + base-path: '' + endpoints: + get: + path: /api/version + method: GET + auth: true + docs: Get version information about the Label Studio instance. + source: + openapi: openapi/openapi.yaml + display-name: Get version information + response: + docs: '' + type: VersionsGetResponse + examples: + - response: + body: + release: release + label-studio-os-package: + key: value + label-studio-os-backend: + key: value + label-studio-frontend: + key: value + dm2: + key: value + label-studio-converter: + key: value + edition: Community + lsf: + key: value + backend: + key: value + audiences: + - public + source: + openapi: openapi/openapi.yaml diff --git a/.mock/fern.config.json b/.mock/fern.config.json index b8268a688..b7c6686cb 100644 --- a/.mock/fern.config.json +++ b/.mock/fern.config.json @@ -1,4 +1,4 @@ { "organization" : "humansignal-org", - "version" : "0.47.4" + "version" : "0.51.7" } \ No newline at end of file diff --git a/.mock/openapi/openapi.yaml b/.mock/openapi/openapi.yaml index 7f3e4dc80..f62c2029d 100644 --- a/.mock/openapi/openapi.yaml +++ b/.mock/openapi/openapi.yaml @@ -2619,401 +2619,6 @@ paths: required: true schema: type: integer - "/api/projects/{id}/export": - get: - operationId: api_projects_export_read - summary: Easy export of tasks and annotations - description: > - - Note: if you have a large project it's recommended to use - - export snapshots, this easy export endpoint might have timeouts.

- - Export annotated tasks as a file in a specific format. - - For example, to export JSON annotations for a project to a file called `annotations.json`, - - run the following from the command line: - - ```bash - - curl -X GET https://localhost:8080/api/projects/{id}/export?exportType=JSON -H 'Authorization: Token abc123' --output 'annotations.json' - - ``` - - To export all tasks, including skipped tasks and others without annotations, run the following from the command line: - - ```bash - - curl -X GET https://localhost:8080/api/projects/{id}/export?exportType=JSON&download_all_tasks=true -H 'Authorization: Token abc123' --output 'annotations.json' - - ``` - - To export specific tasks with IDs of 123 and 345, run the following from the command line: - - ```bash - - curl -X GET https://localhost:8080/api/projects/{id}/export?ids[]=123\&ids[]=345 -H 'Authorization: Token abc123' --output 'annotations.json' - - ``` - parameters: - - name: export_type - in: query - description: Selected export format (JSON by default) - schema: - type: string - - name: download_all_tasks - in: query - description: > - - If true, download all tasks regardless of status. If false, download only annotated tasks. - schema: - type: boolean - - name: download_resources - in: query - description: > - - If true, download all resource files such as images, audio, and others relevant to the tasks. - schema: - type: boolean - - name: ids - in: query - description: > - - Specify a list of task IDs to retrieve only the details for those tasks. - style: form - explode: false - schema: - type: array - items: - title: Task ID - description: Individual task ID - type: integer - - name: id - in: path - description: A unique integer value identifying this project. - required: true - schema: - type: integer - responses: - "200": - description: Exported data - content: - application/json: - schema: - title: Export file - description: Export file with results - type: string - format: binary - tags: - - Export - x-fern-sdk-group-name: projects - x-fern-sdk-method-name: create_export - x-fern-audiences: - - public - parameters: - - name: id - in: path - required: true - schema: - type: string - "/api/projects/{id}/export/formats": - get: - operationId: api_projects_export_formats_read - summary: Get export formats - description: Retrieve the available export formats for the current project by ID. - parameters: - - name: id - in: path - description: A unique integer value identifying this project. - required: true - schema: - type: integer - responses: - "200": - description: Export formats - content: - application/json: - schema: - title: Format list - description: List of available formats - type: array - items: - title: Export format - type: string - tags: - - Export - x-fern-sdk-group-name: - - projects - - exports - x-fern-sdk-method-name: list_formats - x-fern-audiences: - - public - parameters: - - name: id - in: path - required: true - schema: - type: string - "/api/projects/{id}/exports/": - get: - operationId: api_projects_exports_list - summary: List all export snapshots - description: | - - Returns a list of exported files for a specific project by ID. - parameters: - - name: id - in: path - description: A unique integer value identifying this project. - required: true - schema: - type: integer - responses: - "200": - description: "" - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/Export" - tags: - - Export - x-fern-sdk-group-name: - - projects - - exports - x-fern-sdk-method-name: list - x-fern-audiences: - - public - post: - operationId: api_projects_exports_create - summary: Create new export snapshot - description: > - - Create a new export request to start a background task and generate an export file for a specific project by ID. - parameters: - - name: id - in: path - description: A unique integer value identifying this project. - required: true - schema: - type: integer - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ExportCreate" - required: true - responses: - "201": - description: "" - content: - application/json: - schema: - $ref: "#/components/schemas/ExportCreate" - tags: - - Export - x-fern-sdk-group-name: - - projects - - exports - x-fern-sdk-method-name: create - x-fern-audiences: - - public - parameters: - - name: id - in: path - description: A unique integer value identifying this export. - required: true - schema: - type: integer - "/api/projects/{id}/exports/{export_pk}": - get: - operationId: api_projects_exports_read - summary: Get export snapshot by ID - description: > - - Retrieve information about an export file by export ID for a specific project. - parameters: - - name: id - in: path - description: A unique integer value identifying this project. - required: true - schema: - type: integer - - name: export_pk - in: path - description: Primary key identifying the export file. - required: true - schema: - type: string - responses: - "200": - description: "" - content: - application/json: - schema: - $ref: "#/components/schemas/Export" - tags: - - Export - x-fern-sdk-group-name: - - projects - - exports - x-fern-sdk-method-name: get - x-fern-audiences: - - public - delete: - operationId: api_projects_exports_delete - summary: Delete export snapshot - description: | - - Delete an export file by specified export ID. - parameters: - - name: id - in: path - description: A unique integer value identifying this project. - required: true - schema: - type: integer - - name: export_pk - in: path - description: Primary key identifying the export file. - required: true - schema: - type: string - responses: - "204": - description: "" - tags: - - Export - x-fern-sdk-group-name: - - projects - - exports - x-fern-sdk-method-name: delete - x-fern-audiences: - - public - parameters: - - name: id - in: path - description: A unique integer value identifying this export. - required: true - schema: - type: integer - - name: export_pk - in: path - required: true - schema: - type: string - "/api/projects/{id}/exports/{export_pk}/convert": - post: - operationId: api_projects_exports_convert_create - summary: Export conversion - description: | - - Convert export snapshot to selected format - parameters: - - name: id - in: path - description: A unique integer value identifying this project. - required: true - schema: - type: integer - - name: export_pk - in: path - description: Primary key identifying the export file. - required: true - schema: - type: string - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ExportConvert" - required: true - responses: - "201": - description: "" - content: - application/json: - schema: - $ref: "#/components/schemas/ExportConvert" - tags: - - Export - x-fern-sdk-group-name: - - projects - - exports - x-fern-sdk-method-name: convert - x-fern-audiences: - - public - parameters: - - name: id - in: path - description: A unique integer value identifying this export. - required: true - schema: - type: integer - - name: export_pk - in: path - required: true - schema: - type: string - "/api/projects/{id}/exports/{export_pk}/download": - get: - operationId: api_projects_exports_download_read - summary: Download export snapshot as file in specified format - description: > - - Download an export file in the specified format for a specific project. Specify the project ID with the `id` - - parameter in the path and the ID of the export file you want to download using the `export_pk` parameter - - in the path. - - - Get the `export_pk` from the response of the request to [Create new export](/api#operation/api_projects_exports_create) - - or after [listing export files](/api#operation/api_projects_exports_list). - parameters: - - name: exportType - in: query - description: Selected export format - schema: - type: string - - name: id - in: path - description: A unique integer value identifying this project. - required: true - schema: - type: integer - - name: export_pk - in: path - description: Primary key identifying the export file. - required: true - schema: - type: string - responses: - "200": - description: "" - tags: - - Export - x-fern-sdk-group-name: - - projects - - exports - x-fern-sdk-method-name: download - x-fern-audiences: - - public - parameters: - - name: id - in: path - description: A unique integer value identifying this export. - required: true - schema: - type: integer - - name: export_pk - in: path - required: true - schema: - type: string "/api/projects/{id}/file-uploads": get: operationId: api_projects_file-uploads_list @@ -9153,156 +8758,6 @@ components: type: array items: $ref: "#/components/schemas/ConvertedFormat" - TaskFilterOptions: - type: object - properties: - view: - title: View - description: Apply filters from the view ID (a tab from the Data Manager) - type: integer - skipped: - title: Skipped - description: "`only` - include all tasks with skipped annotations
`exclude` - - exclude all tasks with skipped annotations" - type: string - enum: - - only - - exclude - - null - nullable: true - finished: - title: Finished - description: "`only` - include all finished tasks (is_labeled = - true)
`exclude` - exclude all finished tasks" - type: string - enum: - - only - - exclude - - null - nullable: true - annotated: - title: Annotated - description: "`only` - include all tasks with at least one not skipped - annotation
`exclude` - exclude all tasks with at least one not - skipped annotation" - type: string - enum: - - only - - exclude - - null - nullable: true - only_with_annotations: - title: Only with annotations - type: boolean - default: false - AnnotationFilterOptions: - type: object - properties: - usual: - title: Usual - description: Include not skipped and not ground truth annotations - type: boolean - default: true - nullable: true - ground_truth: - title: Ground truth - description: Include ground truth annotations - type: boolean - nullable: true - skipped: - title: Skipped - description: Include skipped annotations - type: boolean - nullable: true - SerializationOption: - description: JSON dict with parameters - type: object - properties: - only_id: - title: Only id - description: Include a full json body or IDs only - type: boolean - default: false - SerializationOptions: - type: object - properties: - drafts: - $ref: "#/components/schemas/SerializationOption" - predictions: - $ref: "#/components/schemas/SerializationOption" - include_annotation_history: - title: Include annotation history - description: Include annotation history - type: boolean - default: false - annotations__completed_by: - $ref: "#/components/schemas/SerializationOption" - interpolate_key_frames: - title: Interpolate key frames - description: Interpolate video key frames - type: boolean - default: false - ExportCreate: - type: object - properties: - title: - title: Title - type: string - maxLength: 2048 - id: - title: ID - type: integer - readOnly: true - created_by: - $ref: "#/components/schemas/UserSimple" - created_at: - title: Created at - description: Creation time - type: string - format: date-time - readOnly: true - finished_at: - title: Finished at - description: Complete or fail time - type: string - format: date-time - nullable: true - status: - title: Export status - type: string - enum: - - created - - in_progress - - failed - - completed - md5: - title: Md5 of file - type: string - maxLength: 128 - minLength: 1 - counters: - title: Exporting meta data - type: object - converted_formats: - type: array - items: - $ref: "#/components/schemas/ConvertedFormat" - task_filter_options: - $ref: "#/components/schemas/TaskFilterOptions" - annotation_filter_options: - $ref: "#/components/schemas/AnnotationFilterOptions" - serialization_options: - $ref: "#/components/schemas/SerializationOptions" - ExportConvert: - required: - - export_type - type: object - properties: - export_type: - title: Export type - description: Export file format. - type: string - minLength: 1 ProjectImport: type: object properties: diff --git a/poetry.lock b/poetry.lock index 0844d48e0..72c5480cd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -79,33 +79,33 @@ tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "black" -version = "24.10.0" +version = "25.1.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" files = [ - {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, - {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, - {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, - {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, - {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, - {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, - {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, - {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, - {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, - {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, - {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, - {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, - {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, - {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, - {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, - {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, - {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, - {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, - {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, - {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, - {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, - {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, + {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, + {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, + {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, + {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, + {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, + {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, + {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, + {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, + {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, + {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, + {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, + {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, + {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, + {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, + {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, + {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, + {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, + {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, + {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, + {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, + {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, + {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, ] [package.dependencies] @@ -125,13 +125,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2024.12.14" +version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, - {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]] @@ -345,13 +345,13 @@ test = ["pytest (>=6)"] [[package]] name = "faker" -version = "35.0.0" +version = "35.2.0" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.8" files = [ - {file = "Faker-35.0.0-py3-none-any.whl", hash = "sha256:926d2301787220e0554c2e39afc4dc535ce4b0a8d0a089657137999f66334ef4"}, - {file = "faker-35.0.0.tar.gz", hash = "sha256:42f2da8cf561e38c72b25e9891168b1e25fec42b6b0b5b0b6cd6041da54af885"}, + {file = "Faker-35.2.0-py3-none-any.whl", hash = "sha256:609abe555761ff31b0e5e16f958696e9b65c9224a7ac612ac96bfc2b8f09fe35"}, + {file = "faker-35.2.0.tar.gz", hash = "sha256:28c24061780f83b45d9cb15a72b8f143b09d276c9ff52eb557744b7a89e8ba19"}, ] [package.dependencies] @@ -1434,13 +1434,13 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2024.2" +version = "2025.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, + {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, + {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, ] [[package]] diff --git a/reference.md b/reference.md index e87e76aa9..23950cd1d 100644 --- a/reference.md +++ b/reference.md @@ -5969,6 +5969,67 @@ client.webhooks.update( + + + + +## Versions +
client.versions.get() +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get version information about the Label Studio instance. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from label_studio_sdk import LabelStudio + +client = LabelStudio( + api_key="YOUR_API_KEY", +) +client.versions.get() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -16992,7 +17053,7 @@ client = LabelStudio( api_key="YOUR_API_KEY", ) client.projects.exports.list( - id=1, + project_id=1, ) ``` @@ -17009,7 +17070,7 @@ client.projects.exports.list(
-**id:** `int` — A unique integer value identifying this project. +**project_id:** `int` — A unique integer value identifying this project.
@@ -17067,7 +17128,7 @@ client = LabelStudio( api_key="YOUR_API_KEY", ) client.projects.exports.create( - id_=1, + project_id=1, ) ``` @@ -17084,7 +17145,7 @@ client.projects.exports.create(
-**id_:** `int` — A unique integer value identifying this project. +**project_id:** `int` — A unique integer value identifying this project.
@@ -17132,7 +17193,7 @@ client.projects.exports.create(
-**status:** `typing.Optional[ExportCreateStatus]` +**status:** `typing.Optional[ExportSnapshotStatus]`
@@ -17238,7 +17299,7 @@ client = LabelStudio( api_key="YOUR_API_KEY", ) client.projects.exports.get( - id=1, + project_id=1, export_pk="export_pk", ) @@ -17256,7 +17317,7 @@ client.projects.exports.get(
-**id:** `int` — A unique integer value identifying this project. +**project_id:** `int` — A unique integer value identifying this project.
@@ -17320,7 +17381,7 @@ client = LabelStudio( api_key="YOUR_API_KEY", ) client.projects.exports.delete( - id=1, + project_id=1, export_pk="export_pk", ) @@ -17338,7 +17399,7 @@ client.projects.exports.delete(
-**id:** `int` — A unique integer value identifying this project. +**project_id:** `int` — A unique integer value identifying this project.
@@ -17406,9 +17467,8 @@ client = LabelStudio( api_key="YOUR_API_KEY", ) client.projects.exports.convert( - id=1, + project_id=1, export_pk="export_pk", - export_type="export_type", ) ``` @@ -17425,7 +17485,7 @@ client.projects.exports.convert(
-**id:** `int` — A unique integer value identifying this project. +**project_id:** `int` — A unique integer value identifying this project.
@@ -17441,91 +17501,7 @@ client.projects.exports.convert(
-**export_type:** `str` — Export file format. - -
-
- -
-
- -**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. - -
-
- - - - - - - - -
client.projects.exports.download(...) -
-
- -#### 📝 Description - -
-
- -
-
- - -Download an export snapshot as a file in a specified format. To see what formats are supported, you can use [Get export formats](list-formats) or see [Export formats supported by Label Studio](https://labelstud.io/guide/export#Export-formats-supported-by-Label-Studio). - -You will need to provide the project ID and export ID (`export_pk`). The export ID is returned when you create the export or you can use [List all export snapshots](list). - -The project ID can be found in the URL when viewing the project in Label Studio, or you can retrieve all project IDs using [List all projects](../list). -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```python -from label_studio_sdk import LabelStudio - -client = LabelStudio( - api_key="YOUR_API_KEY", -) -client.projects.exports.download( - id=1, - export_pk="export_pk", -) - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**id:** `int` — A unique integer value identifying this project. - -
-
- -
-
- -**export_pk:** `str` — Primary key identifying the export file. +**export_type:** `typing.Optional[ExportFormat]`
@@ -17533,7 +17509,7 @@ client.projects.exports.download(
-**export_type:** `typing.Optional[str]` — Selected export format +**download_resources:** `typing.Optional[bool]` — If true, download all resource files such as images, audio, and others relevant to the tasks.
diff --git a/src/label_studio_sdk/__init__.py b/src/label_studio_sdk/__init__.py index 4533e6a85..14bd061c0 100644 --- a/src/label_studio_sdk/__init__.py +++ b/src/label_studio_sdk/__init__.py @@ -23,9 +23,9 @@ DataManagerTaskSerializerDraftsItem, DataManagerTaskSerializerPredictionsItem, Export, - ExportConvert, - ExportCreate, - ExportCreateStatus, + ExportFormat, + ExportSnapshot, + ExportSnapshotStatus, ExportStatus, FileUpload, Filter, @@ -116,6 +116,7 @@ prompts, tasks, users, + versions, views, webhooks, workspaces, @@ -157,6 +158,7 @@ from .tasks import TasksListRequestFields, TasksListResponse from .users import UsersGetTokenResponse, UsersResetTokenResponse from .version import __version__ +from .versions import VersionsGetResponse, VersionsGetResponseEdition from .views import ( ViewsCreateRequestData, ViewsCreateRequestDataFilters, @@ -216,9 +218,9 @@ "DataManagerTaskSerializerDraftsItem", "DataManagerTaskSerializerPredictionsItem", "Export", - "ExportConvert", - "ExportCreate", - "ExportCreateStatus", + "ExportFormat", + "ExportSnapshot", + "ExportSnapshotStatus", "ExportStatus", "ExportStorageListTypesResponseItem", "FileUpload", @@ -310,6 +312,8 @@ "UserSimple", "UsersGetTokenResponse", "UsersResetTokenResponse", + "VersionsGetResponse", + "VersionsGetResponseEdition", "View", "ViewsCreateRequestData", "ViewsCreateRequestDataFilters", @@ -347,6 +351,7 @@ "prompts", "tasks", "users", + "versions", "views", "webhooks", "workspaces", diff --git a/src/label_studio_sdk/_extensions/pager_ext.py b/src/label_studio_sdk/_extensions/pager_ext.py index 13b516e0a..056599805 100644 --- a/src/label_studio_sdk/_extensions/pager_ext.py +++ b/src/label_studio_sdk/_extensions/pager_ext.py @@ -47,3 +47,11 @@ async def __aiter__(self) -> typing.AsyncIterator[T]: # type: ignore if exc.status_code == 404: return raise + + async def __anext__(self) -> T: + try: + return await super().__anext__() + except ApiError as exc: + if exc.status_code == 404: + raise StopAsyncIteration + raise diff --git a/src/label_studio_sdk/base_client.py b/src/label_studio_sdk/base_client.py index 30cf6ec0a..59e74593d 100644 --- a/src/label_studio_sdk/base_client.py +++ b/src/label_studio_sdk/base_client.py @@ -18,6 +18,7 @@ from .import_storage.client import ImportStorageClient from .export_storage.client import ExportStorageClient from .webhooks.client import WebhooksClient +from .versions.client import VersionsClient from .prompts.client import PromptsClient from .model_providers.client import ModelProvidersClient from .comments.client import CommentsClient @@ -35,6 +36,7 @@ from .import_storage.client import AsyncImportStorageClient from .export_storage.client import AsyncExportStorageClient from .webhooks.client import AsyncWebhooksClient +from .versions.client import AsyncVersionsClient from .prompts.client import AsyncPromptsClient from .model_providers.client import AsyncModelProvidersClient from .comments.client import AsyncCommentsClient @@ -115,6 +117,7 @@ def __init__( self.import_storage = ImportStorageClient(client_wrapper=self._client_wrapper) self.export_storage = ExportStorageClient(client_wrapper=self._client_wrapper) self.webhooks = WebhooksClient(client_wrapper=self._client_wrapper) + self.versions = VersionsClient(client_wrapper=self._client_wrapper) self.prompts = PromptsClient(client_wrapper=self._client_wrapper) self.model_providers = ModelProvidersClient(client_wrapper=self._client_wrapper) self.comments = CommentsClient(client_wrapper=self._client_wrapper) @@ -195,6 +198,7 @@ def __init__( self.import_storage = AsyncImportStorageClient(client_wrapper=self._client_wrapper) self.export_storage = AsyncExportStorageClient(client_wrapper=self._client_wrapper) self.webhooks = AsyncWebhooksClient(client_wrapper=self._client_wrapper) + self.versions = AsyncVersionsClient(client_wrapper=self._client_wrapper) self.prompts = AsyncPromptsClient(client_wrapper=self._client_wrapper) self.model_providers = AsyncModelProvidersClient(client_wrapper=self._client_wrapper) self.comments = AsyncCommentsClient(client_wrapper=self._client_wrapper) diff --git a/src/label_studio_sdk/client.py b/src/label_studio_sdk/client.py index e0b14687e..89ae64204 100644 --- a/src/label_studio_sdk/client.py +++ b/src/label_studio_sdk/client.py @@ -1,6 +1,7 @@ from .base_client import LabelStudioBase, AsyncLabelStudioBase from .tasks.client_ext import TasksClientExt, AsyncTasksClientExt from .projects.client_ext import ProjectsClientExt, AsyncProjectsClientExt +from .core.api_error import ApiError class LabelStudio(LabelStudioBase): diff --git a/src/label_studio_sdk/projects/__init__.py b/src/label_studio_sdk/projects/__init__.py index 09c1a38e5..c3a305e05 100644 --- a/src/label_studio_sdk/projects/__init__.py +++ b/src/label_studio_sdk/projects/__init__.py @@ -2,8 +2,11 @@ from .types import ProjectsCreateResponse, ProjectsImportTasksResponse, ProjectsListResponse, ProjectsUpdateResponse from . import exports +from .exports import ExportsConvertResponse, ExportsListFormatsResponseItem __all__ = [ + "ExportsConvertResponse", + "ExportsListFormatsResponseItem", "ProjectsCreateResponse", "ProjectsImportTasksResponse", "ProjectsListResponse", diff --git a/src/label_studio_sdk/projects/client_ext.py b/src/label_studio_sdk/projects/client_ext.py index ab5589353..37c197d5e 100644 --- a/src/label_studio_sdk/projects/client_ext.py +++ b/src/label_studio_sdk/projects/client_ext.py @@ -5,6 +5,7 @@ from label_studio_sdk._extensions.pager_ext import SyncPagerExt, AsyncPagerExt, T from label_studio_sdk.types.project import Project from label_studio_sdk.label_interface import LabelInterface +from .exports.client_ext import ExportsClientExt, AsyncExportsClientExt from ..core import RequestOptions @@ -16,6 +17,10 @@ def get_label_interface(self): class ProjectsClientExt(ProjectsClient): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.exports = ExportsClientExt(client_wrapper=self._client_wrapper) def list(self, **kwargs) -> SyncPagerExt[T]: return SyncPagerExt.from_sync_pager(super().list(**kwargs)) @@ -24,10 +29,21 @@ def list(self, **kwargs) -> SyncPagerExt[T]: def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> ProjectExt: return ProjectExt(**dict(super().get(id, request_options=request_options))) + + get.__doc__ = ProjectsClient.get.__doc__ class AsyncProjectsClientExt(AsyncProjectsClient): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.exports = AsyncExportsClientExt(client_wrapper=self._client_wrapper) + + async def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> ProjectExt: + return ProjectExt(**dict(await super().get(id, request_options=request_options))) + + get.__doc__ = AsyncProjectsClient.get.__doc__ + async def list(self, **kwargs): return await AsyncPagerExt.from_async_pager(await super().list(**kwargs)) diff --git a/src/label_studio_sdk/projects/exports/__init__.py b/src/label_studio_sdk/projects/exports/__init__.py index f3ea2659b..e251c825b 100644 --- a/src/label_studio_sdk/projects/exports/__init__.py +++ b/src/label_studio_sdk/projects/exports/__init__.py @@ -1,2 +1,5 @@ # This file was auto-generated by Fern from our API Definition. +from .types import ExportsConvertResponse, ExportsListFormatsResponseItem + +__all__ = ["ExportsConvertResponse", "ExportsListFormatsResponseItem"] diff --git a/src/label_studio_sdk/projects/exports/client.py b/src/label_studio_sdk/projects/exports/client.py index 83f7bfa15..85e7ee557 100644 --- a/src/label_studio_sdk/projects/exports/client.py +++ b/src/label_studio_sdk/projects/exports/client.py @@ -6,18 +6,20 @@ from ...core.jsonable_encoder import jsonable_encoder from json.decoder import JSONDecodeError from ...core.api_error import ApiError +from .types.exports_list_formats_response_item import ExportsListFormatsResponseItem from ...core.pydantic_utilities import parse_obj_as from ...types.export import Export from ...types.user_simple import UserSimple import datetime as dt -from ...types.export_create_status import ExportCreateStatus +from ...types.export_snapshot_status import ExportSnapshotStatus from ...types.converted_format import ConvertedFormat from ...types.task_filter_options import TaskFilterOptions from ...types.annotation_filter_options import AnnotationFilterOptions from ...types.serialization_options import SerializationOptions -from ...types.export_create import ExportCreate +from ...types.export_snapshot import ExportSnapshot from ...core.serialization import convert_and_respect_annotation_metadata -from ...types.export_convert import ExportConvert +from ...types.export_format import ExportFormat +from .types.exports_convert_response import ExportsConvertResponse from ...core.client_wrapper import AsyncClientWrapper # this is used as the default value for optional parameters @@ -28,7 +30,7 @@ class ExportsClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper - def create_export( + def download_sync( self, id: int, *, @@ -84,7 +86,7 @@ def create_export( Yields ------ typing.Iterator[bytes] - Exported data + Exported data in binary format """ with self._client_wrapper.httpx_client.stream( f"api/projects/{jsonable_encoder(id)}/export", @@ -109,7 +111,9 @@ def create_export( raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) - def list_formats(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> typing.List[str]: + def list_formats( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[ExportsListFormatsResponseItem]: """ Before exporting annotations, you can check with formats are supported by the specified project. For more information about export formats, see [Export formats supported by Label Studio](https://labelstud.io/guide/export#Export-formats-supported-by-Label-Studio). @@ -126,7 +130,7 @@ def list_formats(self, id: int, *, request_options: typing.Optional[RequestOptio Returns ------- - typing.List[str] + typing.List[ExportsListFormatsResponseItem] Export formats Examples @@ -148,9 +152,9 @@ def list_formats(self, id: int, *, request_options: typing.Optional[RequestOptio try: if 200 <= _response.status_code < 300: return typing.cast( - typing.List[str], + typing.List[ExportsListFormatsResponseItem], parse_obj_as( - type_=typing.List[str], # type: ignore + type_=typing.List[ExportsListFormatsResponseItem], # type: ignore object_=_response.json(), ), ) @@ -159,7 +163,7 @@ def list_formats(self, id: int, *, request_options: typing.Optional[RequestOptio raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) - def list(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> typing.List[Export]: + def list(self, project_id: int, *, request_options: typing.Optional[RequestOptions] = None) -> typing.List[Export]: """ Returns a list of export file (snapshots) for a specific project by ID. The project ID can be found in the URL when viewing the project in Label Studio, or you can retrieve all project IDs using [List all projects](../list). @@ -168,7 +172,7 @@ def list(self, id: int, *, request_options: typing.Optional[RequestOptions] = No Parameters ---------- - id : int + project_id : int A unique integer value identifying this project. request_options : typing.Optional[RequestOptions] @@ -187,11 +191,11 @@ def list(self, id: int, *, request_options: typing.Optional[RequestOptions] = No api_key="YOUR_API_KEY", ) client.projects.exports.list( - id=1, + project_id=1, ) """ _response = self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id)}/exports/", + f"api/projects/{jsonable_encoder(project_id)}/exports", method="GET", request_options=request_options, ) @@ -211,14 +215,14 @@ def list(self, id: int, *, request_options: typing.Optional[RequestOptions] = No def create( self, - id_: int, + project_id: int, *, title: typing.Optional[str] = OMIT, id: typing.Optional[int] = OMIT, created_by: typing.Optional[UserSimple] = OMIT, created_at: typing.Optional[dt.datetime] = OMIT, finished_at: typing.Optional[dt.datetime] = OMIT, - status: typing.Optional[ExportCreateStatus] = OMIT, + status: typing.Optional[ExportSnapshotStatus] = OMIT, md5: typing.Optional[str] = OMIT, counters: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT, converted_formats: typing.Optional[typing.Sequence[ConvertedFormat]] = OMIT, @@ -226,7 +230,7 @@ def create( annotation_filter_options: typing.Optional[AnnotationFilterOptions] = OMIT, serialization_options: typing.Optional[SerializationOptions] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> ExportCreate: + ) -> ExportSnapshot: """ Create a new export request to start a background task and generate an export file (snapshot) for a specific project by ID. The project ID can be found in the URL when viewing the project in Label Studio, or you can retrieve all project IDs using [List all projects](../list). @@ -237,7 +241,7 @@ def create( Parameters ---------- - id_ : int + project_id : int A unique integer value identifying this project. title : typing.Optional[str] @@ -252,7 +256,7 @@ def create( finished_at : typing.Optional[dt.datetime] Complete or fail time - status : typing.Optional[ExportCreateStatus] + status : typing.Optional[ExportSnapshotStatus] md5 : typing.Optional[str] @@ -271,7 +275,7 @@ def create( Returns ------- - ExportCreate + ExportSnapshot Examples @@ -282,11 +286,11 @@ def create( api_key="YOUR_API_KEY", ) client.projects.exports.create( - id_=1, + project_id=1, ) """ _response = self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id_)}/exports/", + f"api/projects/{jsonable_encoder(project_id)}/exports", method="POST", json={ "title": title, @@ -318,9 +322,9 @@ def create( try: if 200 <= _response.status_code < 300: return typing.cast( - ExportCreate, + ExportSnapshot, parse_obj_as( - type_=ExportCreate, # type: ignore + type_=ExportSnapshot, # type: ignore object_=_response.json(), ), ) @@ -329,7 +333,64 @@ def create( raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) - def get(self, id: int, export_pk: str, *, request_options: typing.Optional[RequestOptions] = None) -> Export: + def download( + self, + project_id: int, + export_pk: str, + *, + export_type: typing.Optional[ExportFormat] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Iterator[bytes]: + """ + + Download an export snapshot as a file in a specified format. To see what formats are supported, you can use [Get export formats](list-formats) or see [Export formats supported by Label Studio](https://labelstud.io/guide/export#Export-formats-supported-by-Label-Studio). + + You will need to provide the project ID and export ID (`export_pk`). The export ID is returned when you create the export or you can use [List all export snapshots](list). + + The project ID can be found in the URL when viewing the project in Label Studio, or you can retrieve all project IDs using [List all projects](../list). + + Parameters + ---------- + project_id : int + A unique integer value identifying this project. + + export_pk : str + Primary key identifying the export file. + + export_type : typing.Optional[ExportFormat] + Selected export format. JSON is available by default. For other formats, you need to convert the export first. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Yields + ------ + typing.Iterator[bytes] + Exported data in binary format + """ + with self._client_wrapper.httpx_client.stream( + f"api/projects/{jsonable_encoder(project_id)}/exports/{jsonable_encoder(export_pk)}/download", + method="GET", + params={ + "exportType": export_type, + }, + request_options=request_options, + ) as _response: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + for _chunk in _response.iter_bytes(chunk_size=_chunk_size): + yield _chunk + return + _response.read() + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def get( + self, project_id: int, export_pk: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> Export: """ Retrieve information about a specific export file (snapshot). @@ -340,7 +401,7 @@ def get(self, id: int, export_pk: str, *, request_options: typing.Optional[Reque Parameters ---------- - id : int + project_id : int A unique integer value identifying this project. export_pk : str @@ -362,12 +423,12 @@ def get(self, id: int, export_pk: str, *, request_options: typing.Optional[Reque api_key="YOUR_API_KEY", ) client.projects.exports.get( - id=1, + project_id=1, export_pk="export_pk", ) """ _response = self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id)}/exports/{jsonable_encoder(export_pk)}", + f"api/projects/{jsonable_encoder(project_id)}/exports/{jsonable_encoder(export_pk)}", method="GET", request_options=request_options, ) @@ -385,7 +446,9 @@ def get(self, id: int, export_pk: str, *, request_options: typing.Optional[Reque raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) - def delete(self, id: int, export_pk: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: + def delete( + self, project_id: int, export_pk: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> None: """ Delete an export file by specified export ID. @@ -394,7 +457,7 @@ def delete(self, id: int, export_pk: str, *, request_options: typing.Optional[Re Parameters ---------- - id : int + project_id : int A unique integer value identifying this project. export_pk : str @@ -415,12 +478,12 @@ def delete(self, id: int, export_pk: str, *, request_options: typing.Optional[Re api_key="YOUR_API_KEY", ) client.projects.exports.delete( - id=1, + project_id=1, export_pk="export_pk", ) """ _response = self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id)}/exports/{jsonable_encoder(export_pk)}", + f"api/projects/{jsonable_encoder(project_id)}/exports/{jsonable_encoder(export_pk)}", method="DELETE", request_options=request_options, ) @@ -433,8 +496,14 @@ def delete(self, id: int, export_pk: str, *, request_options: typing.Optional[Re raise ApiError(status_code=_response.status_code, body=_response_json) def convert( - self, id: int, export_pk: str, *, export_type: str, request_options: typing.Optional[RequestOptions] = None - ) -> ExportConvert: + self, + project_id: int, + export_pk: str, + *, + export_type: typing.Optional[ExportFormat] = OMIT, + download_resources: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ExportsConvertResponse: """ You can use this to convert an export snapshot into the selected format. @@ -447,21 +516,23 @@ def convert( Parameters ---------- - id : int + project_id : int A unique integer value identifying this project. export_pk : str Primary key identifying the export file. - export_type : str - Export file format. + export_type : typing.Optional[ExportFormat] + + download_resources : typing.Optional[bool] + If true, download all resource files such as images, audio, and others relevant to the tasks. request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - ExportConvert + ExportsConvertResponse Examples @@ -472,16 +543,19 @@ def convert( api_key="YOUR_API_KEY", ) client.projects.exports.convert( - id=1, + project_id=1, export_pk="export_pk", - export_type="export_type", ) """ _response = self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id)}/exports/{jsonable_encoder(export_pk)}/convert", + f"api/projects/{jsonable_encoder(project_id)}/exports/{jsonable_encoder(export_pk)}/convert", method="POST", json={ "export_type": export_type, + "download_resources": download_resources, + }, + headers={ + "content-type": "application/json", }, request_options=request_options, omit=OMIT, @@ -489,9 +563,9 @@ def convert( try: if 200 <= _response.status_code < 300: return typing.cast( - ExportConvert, + ExportsConvertResponse, parse_obj_as( - type_=ExportConvert, # type: ignore + type_=ExportsConvertResponse, # type: ignore object_=_response.json(), ), ) @@ -500,74 +574,12 @@ def convert( raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) - def download( - self, - id: int, - export_pk: str, - *, - export_type: typing.Optional[str] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> None: - """ - - Download an export snapshot as a file in a specified format. To see what formats are supported, you can use [Get export formats](list-formats) or see [Export formats supported by Label Studio](https://labelstud.io/guide/export#Export-formats-supported-by-Label-Studio). - - You will need to provide the project ID and export ID (`export_pk`). The export ID is returned when you create the export or you can use [List all export snapshots](list). - - The project ID can be found in the URL when viewing the project in Label Studio, or you can retrieve all project IDs using [List all projects](../list). - - Parameters - ---------- - id : int - A unique integer value identifying this project. - - export_pk : str - Primary key identifying the export file. - - export_type : typing.Optional[str] - Selected export format - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - None - - Examples - -------- - from label_studio_sdk import LabelStudio - - client = LabelStudio( - api_key="YOUR_API_KEY", - ) - client.projects.exports.download( - id=1, - export_pk="export_pk", - ) - """ - _response = self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id)}/exports/{jsonable_encoder(export_pk)}/download", - method="GET", - params={ - "exportType": export_type, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) - class AsyncExportsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper - async def create_export( + async def download_sync( self, id: int, *, @@ -623,7 +635,7 @@ async def create_export( Yields ------ typing.AsyncIterator[bytes] - Exported data + Exported data in binary format """ async with self._client_wrapper.httpx_client.stream( f"api/projects/{jsonable_encoder(id)}/export", @@ -650,7 +662,7 @@ async def create_export( async def list_formats( self, id: int, *, request_options: typing.Optional[RequestOptions] = None - ) -> typing.List[str]: + ) -> typing.List[ExportsListFormatsResponseItem]: """ Before exporting annotations, you can check with formats are supported by the specified project. For more information about export formats, see [Export formats supported by Label Studio](https://labelstud.io/guide/export#Export-formats-supported-by-Label-Studio). @@ -667,7 +679,7 @@ async def list_formats( Returns ------- - typing.List[str] + typing.List[ExportsListFormatsResponseItem] Export formats Examples @@ -697,9 +709,9 @@ async def main() -> None: try: if 200 <= _response.status_code < 300: return typing.cast( - typing.List[str], + typing.List[ExportsListFormatsResponseItem], parse_obj_as( - type_=typing.List[str], # type: ignore + type_=typing.List[ExportsListFormatsResponseItem], # type: ignore object_=_response.json(), ), ) @@ -708,7 +720,9 @@ async def main() -> None: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) - async def list(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> typing.List[Export]: + async def list( + self, project_id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[Export]: """ Returns a list of export file (snapshots) for a specific project by ID. The project ID can be found in the URL when viewing the project in Label Studio, or you can retrieve all project IDs using [List all projects](../list). @@ -717,7 +731,7 @@ async def list(self, id: int, *, request_options: typing.Optional[RequestOptions Parameters ---------- - id : int + project_id : int A unique integer value identifying this project. request_options : typing.Optional[RequestOptions] @@ -741,14 +755,14 @@ async def list(self, id: int, *, request_options: typing.Optional[RequestOptions async def main() -> None: await client.projects.exports.list( - id=1, + project_id=1, ) asyncio.run(main()) """ _response = await self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id)}/exports/", + f"api/projects/{jsonable_encoder(project_id)}/exports", method="GET", request_options=request_options, ) @@ -768,14 +782,14 @@ async def main() -> None: async def create( self, - id_: int, + project_id: int, *, title: typing.Optional[str] = OMIT, id: typing.Optional[int] = OMIT, created_by: typing.Optional[UserSimple] = OMIT, created_at: typing.Optional[dt.datetime] = OMIT, finished_at: typing.Optional[dt.datetime] = OMIT, - status: typing.Optional[ExportCreateStatus] = OMIT, + status: typing.Optional[ExportSnapshotStatus] = OMIT, md5: typing.Optional[str] = OMIT, counters: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT, converted_formats: typing.Optional[typing.Sequence[ConvertedFormat]] = OMIT, @@ -783,7 +797,7 @@ async def create( annotation_filter_options: typing.Optional[AnnotationFilterOptions] = OMIT, serialization_options: typing.Optional[SerializationOptions] = OMIT, request_options: typing.Optional[RequestOptions] = None, - ) -> ExportCreate: + ) -> ExportSnapshot: """ Create a new export request to start a background task and generate an export file (snapshot) for a specific project by ID. The project ID can be found in the URL when viewing the project in Label Studio, or you can retrieve all project IDs using [List all projects](../list). @@ -794,7 +808,7 @@ async def create( Parameters ---------- - id_ : int + project_id : int A unique integer value identifying this project. title : typing.Optional[str] @@ -809,7 +823,7 @@ async def create( finished_at : typing.Optional[dt.datetime] Complete or fail time - status : typing.Optional[ExportCreateStatus] + status : typing.Optional[ExportSnapshotStatus] md5 : typing.Optional[str] @@ -828,7 +842,7 @@ async def create( Returns ------- - ExportCreate + ExportSnapshot Examples @@ -844,14 +858,14 @@ async def create( async def main() -> None: await client.projects.exports.create( - id_=1, + project_id=1, ) asyncio.run(main()) """ _response = await self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id_)}/exports/", + f"api/projects/{jsonable_encoder(project_id)}/exports", method="POST", json={ "title": title, @@ -883,9 +897,9 @@ async def main() -> None: try: if 200 <= _response.status_code < 300: return typing.cast( - ExportCreate, + ExportSnapshot, parse_obj_as( - type_=ExportCreate, # type: ignore + type_=ExportSnapshot, # type: ignore object_=_response.json(), ), ) @@ -894,7 +908,64 @@ async def main() -> None: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) - async def get(self, id: int, export_pk: str, *, request_options: typing.Optional[RequestOptions] = None) -> Export: + async def download( + self, + project_id: int, + export_pk: str, + *, + export_type: typing.Optional[ExportFormat] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.AsyncIterator[bytes]: + """ + + Download an export snapshot as a file in a specified format. To see what formats are supported, you can use [Get export formats](list-formats) or see [Export formats supported by Label Studio](https://labelstud.io/guide/export#Export-formats-supported-by-Label-Studio). + + You will need to provide the project ID and export ID (`export_pk`). The export ID is returned when you create the export or you can use [List all export snapshots](list). + + The project ID can be found in the URL when viewing the project in Label Studio, or you can retrieve all project IDs using [List all projects](../list). + + Parameters + ---------- + project_id : int + A unique integer value identifying this project. + + export_pk : str + Primary key identifying the export file. + + export_type : typing.Optional[ExportFormat] + Selected export format. JSON is available by default. For other formats, you need to convert the export first. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Yields + ------ + typing.AsyncIterator[bytes] + Exported data in binary format + """ + async with self._client_wrapper.httpx_client.stream( + f"api/projects/{jsonable_encoder(project_id)}/exports/{jsonable_encoder(export_pk)}/download", + method="GET", + params={ + "exportType": export_type, + }, + request_options=request_options, + ) as _response: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size): + yield _chunk + return + await _response.aread() + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get( + self, project_id: int, export_pk: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> Export: """ Retrieve information about a specific export file (snapshot). @@ -905,7 +976,7 @@ async def get(self, id: int, export_pk: str, *, request_options: typing.Optional Parameters ---------- - id : int + project_id : int A unique integer value identifying this project. export_pk : str @@ -932,7 +1003,7 @@ async def get(self, id: int, export_pk: str, *, request_options: typing.Optional async def main() -> None: await client.projects.exports.get( - id=1, + project_id=1, export_pk="export_pk", ) @@ -940,7 +1011,7 @@ async def main() -> None: asyncio.run(main()) """ _response = await self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id)}/exports/{jsonable_encoder(export_pk)}", + f"api/projects/{jsonable_encoder(project_id)}/exports/{jsonable_encoder(export_pk)}", method="GET", request_options=request_options, ) @@ -958,7 +1029,9 @@ async def main() -> None: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) - async def delete(self, id: int, export_pk: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: + async def delete( + self, project_id: int, export_pk: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> None: """ Delete an export file by specified export ID. @@ -967,7 +1040,7 @@ async def delete(self, id: int, export_pk: str, *, request_options: typing.Optio Parameters ---------- - id : int + project_id : int A unique integer value identifying this project. export_pk : str @@ -993,7 +1066,7 @@ async def delete(self, id: int, export_pk: str, *, request_options: typing.Optio async def main() -> None: await client.projects.exports.delete( - id=1, + project_id=1, export_pk="export_pk", ) @@ -1001,7 +1074,7 @@ async def main() -> None: asyncio.run(main()) """ _response = await self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id)}/exports/{jsonable_encoder(export_pk)}", + f"api/projects/{jsonable_encoder(project_id)}/exports/{jsonable_encoder(export_pk)}", method="DELETE", request_options=request_options, ) @@ -1014,8 +1087,14 @@ async def main() -> None: raise ApiError(status_code=_response.status_code, body=_response_json) async def convert( - self, id: int, export_pk: str, *, export_type: str, request_options: typing.Optional[RequestOptions] = None - ) -> ExportConvert: + self, + project_id: int, + export_pk: str, + *, + export_type: typing.Optional[ExportFormat] = OMIT, + download_resources: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ExportsConvertResponse: """ You can use this to convert an export snapshot into the selected format. @@ -1028,21 +1107,23 @@ async def convert( Parameters ---------- - id : int + project_id : int A unique integer value identifying this project. export_pk : str Primary key identifying the export file. - export_type : str - Export file format. + export_type : typing.Optional[ExportFormat] + + download_resources : typing.Optional[bool] + If true, download all resource files such as images, audio, and others relevant to the tasks. request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - ExportConvert + ExportsConvertResponse Examples @@ -1058,19 +1139,22 @@ async def convert( async def main() -> None: await client.projects.exports.convert( - id=1, + project_id=1, export_pk="export_pk", - export_type="export_type", ) asyncio.run(main()) """ _response = await self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id)}/exports/{jsonable_encoder(export_pk)}/convert", + f"api/projects/{jsonable_encoder(project_id)}/exports/{jsonable_encoder(export_pk)}/convert", method="POST", json={ "export_type": export_type, + "download_resources": download_resources, + }, + headers={ + "content-type": "application/json", }, request_options=request_options, omit=OMIT, @@ -1078,9 +1162,9 @@ async def main() -> None: try: if 200 <= _response.status_code < 300: return typing.cast( - ExportConvert, + ExportsConvertResponse, parse_obj_as( - type_=ExportConvert, # type: ignore + type_=ExportsConvertResponse, # type: ignore object_=_response.json(), ), ) @@ -1088,73 +1172,3 @@ async def main() -> None: except JSONDecodeError: raise ApiError(status_code=_response.status_code, body=_response.text) raise ApiError(status_code=_response.status_code, body=_response_json) - - async def download( - self, - id: int, - export_pk: str, - *, - export_type: typing.Optional[str] = None, - request_options: typing.Optional[RequestOptions] = None, - ) -> None: - """ - - Download an export snapshot as a file in a specified format. To see what formats are supported, you can use [Get export formats](list-formats) or see [Export formats supported by Label Studio](https://labelstud.io/guide/export#Export-formats-supported-by-Label-Studio). - - You will need to provide the project ID and export ID (`export_pk`). The export ID is returned when you create the export or you can use [List all export snapshots](list). - - The project ID can be found in the URL when viewing the project in Label Studio, or you can retrieve all project IDs using [List all projects](../list). - - Parameters - ---------- - id : int - A unique integer value identifying this project. - - export_pk : str - Primary key identifying the export file. - - export_type : typing.Optional[str] - Selected export format - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - None - - Examples - -------- - import asyncio - - from label_studio_sdk import AsyncLabelStudio - - client = AsyncLabelStudio( - api_key="YOUR_API_KEY", - ) - - - async def main() -> None: - await client.projects.exports.download( - id=1, - export_pk="export_pk", - ) - - - asyncio.run(main()) - """ - _response = await self._client_wrapper.httpx_client.request( - f"api/projects/{jsonable_encoder(id)}/exports/{jsonable_encoder(export_pk)}/download", - method="GET", - params={ - "exportType": export_type, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - return - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, body=_response.text) - raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/src/label_studio_sdk/projects/exports/client_ext.py b/src/label_studio_sdk/projects/exports/client_ext.py new file mode 100644 index 000000000..549f08a92 --- /dev/null +++ b/src/label_studio_sdk/projects/exports/client_ext.py @@ -0,0 +1,134 @@ +import json +import time +import asyncio +import typing +import pandas as pd +from .client import ExportsClient, AsyncExportsClient +from io import BytesIO +from label_studio_sdk.versions.client import VersionsClient, AsyncVersionsClient +from label_studio_sdk.core.api_error import ApiError + + +class ExportTimeoutError(ApiError): + + def __init__(self, export_snapshot): + super().__init__( + status_code=500, + body=( + f"Export job timed out after {timeout} seconds: " + f"unable to retrieve export job {export_snapshot.id}. " + f"Current status: {export_snapshot.status}. " + f"Try manually checking the running job with " + f"`ls.projects.exports.get(project_id={project_id}, export_pk={export_snapshot.id})`." + ) + ) + + +def _bytestream_to_fileobj(bytestream: typing.Iterable[bytes]) -> typing.BinaryIO: + buffer = BytesIO() + for chunk in bytestream: + buffer.write(chunk) + buffer.seek(0) + return buffer + +def _bytestream_to_binary(bytestream: typing.Iterable[bytes]) -> bytes: + fileobj = _bytestream_to_fileobj(bytestream) + return fileobj.getvalue() + +def _bytestream_to_json(bytestream: typing.Iterable[bytes]) -> dict: + fileobj = _bytestream_to_fileobj(bytestream) + return json.load(fileobj) + +def _bytestream_to_pandas(bytestream: typing.Iterable[bytes]) -> pd.DataFrame: + fileobj = _bytestream_to_fileobj(bytestream) + return pd.read_csv(fileobj) + +class ExportsClientExt(ExportsClient): + + def _get_bytestream( + self, + project_id: int, + export_type: str, + timeout: int = 60, + create_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, + convert_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, + download_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, + ): + version = VersionsClient(client_wrapper=self._client_wrapper).get() + + if version.edition == "Enterprise": + # Enterprise edition exports are async, so we need to wait for the export job to complete + export_snapshot = self.create(project_id, **(create_kwargs or {})) + if export_type != "JSON": + self.convert(project_id, export_pk=export_snapshot.id, export_type=export_type, **(convert_kwargs or {})) + start_time = time.time() + while export_snapshot.status != "completed": + export_snapshot = self.get(project_id, export_pk=export_snapshot.id) + if time.time() - start_time > timeout: + raise ExportTimeoutError(export_snapshot) + time.sleep(1) + bytestream = self.download(project_id, export_pk=export_snapshot.id, export_type=export_type, request_options={'chunk_size': 1024}, **(download_kwargs or {})) + else: + # Community edition exports are sync, so we can download the file immediately + bytestream = self.download_sync(project_id, export_type=export_type, download_all_tasks=True, download_resources=True) + return bytestream + + def as_file(self, project_id: int, export_type: str = "JSON", timeout: int = 60, create_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, convert_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, download_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None): + bytestream = self._get_bytestream(project_id, export_type, timeout, create_kwargs, convert_kwargs, download_kwargs) + return _bytestream_to_fileobj(bytestream) + + def as_binary(self, project_id: int, export_type: str = "JSON", timeout: int = 60, create_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, convert_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, download_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None): + bytestream = self._get_bytestream(project_id, export_type, timeout, create_kwargs, convert_kwargs, download_kwargs) + return _bytestream_to_binary(bytestream) + + def as_json(self, project_id: int, timeout: int = 60, create_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, convert_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, download_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None): + bytestream = self._get_bytestream(project_id, "JSON", timeout, create_kwargs, convert_kwargs, download_kwargs) + return _bytestream_to_json(bytestream) + + def as_pandas(self, project_id: int, timeout: int = 60, create_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, convert_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, download_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None): + bytestream = self._get_bytestream(project_id, "CSV", timeout, create_kwargs, convert_kwargs, download_kwargs) + return _bytestream_to_pandas(bytestream) + +class AsyncExportsClientExt(AsyncExportsClient): + + async def _get_bytestream( + self, + project_id: int, + export_type: str, + timeout: int = 60, + create_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, + convert_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, + download_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, + ): + version = await AsyncVersionsClient(client_wrapper=self._client_wrapper).get() + if version.edition == "Enterprise": + # Enterprise edition exports are async, so we need to wait for the export job to complete + export_snapshot = await self.create(project_id, **(create_kwargs or {})) + if export_type != "JSON": + await self.convert(project_id, export_pk=export_snapshot.id, export_type=export_type, **(convert_kwargs or {})) + start_time = time.time() + while export_snapshot.status != "completed": + export_snapshot = await self.get(project_id, export_pk=export_snapshot.id) + if time.time() - start_time > timeout: + raise ExportTimeoutError(export_snapshot) + await asyncio.sleep(1) + bytestream = await self.download(project_id, export_pk=export_snapshot.id, export_type=export_type, request_options={'chunk_size': 1024}, **(download_kwargs or {})) + else: + bytestream = await self.download_sync(project_id, export_type=export_type, download_all_tasks=True, download_resources=True) + return bytestream + + async def as_file(self, project_id: int, export_type: str = "JSON", timeout: int = 60, create_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, convert_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, download_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None): + bytestream = await self._get_bytestream(project_id, export_type, timeout, create_kwargs, convert_kwargs, download_kwargs) + return _bytestream_to_fileobj(bytestream) + + async def as_binary(self, project_id: int, export_type: str = "JSON", timeout: int = 60, create_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, convert_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, download_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None): + bytestream = await self._get_bytestream(project_id, export_type, timeout, create_kwargs, convert_kwargs, download_kwargs) + return _bytestream_to_binary(bytestream) + + async def as_json(self, project_id: int, timeout: int = 60, create_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, convert_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, download_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None): + bytestream = await self._get_bytestream(project_id, "JSON", timeout, create_kwargs, convert_kwargs, download_kwargs) + return _bytestream_to_json(bytestream) + + async def as_pandas(self, project_id: int, timeout: int = 60, create_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, convert_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None, download_kwargs: typing.Optional[typing.Dict[str, typing.Any]] = None): + bytestream = await self._get_bytestream(project_id, "CSV", timeout, create_kwargs, convert_kwargs, download_kwargs) + return _bytestream_to_pandas(bytestream) diff --git a/src/label_studio_sdk/projects/exports/types/__init__.py b/src/label_studio_sdk/projects/exports/types/__init__.py new file mode 100644 index 000000000..8ac8e7b26 --- /dev/null +++ b/src/label_studio_sdk/projects/exports/types/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from .exports_convert_response import ExportsConvertResponse +from .exports_list_formats_response_item import ExportsListFormatsResponseItem + +__all__ = ["ExportsConvertResponse", "ExportsListFormatsResponseItem"] diff --git a/src/label_studio_sdk/types/export_convert.py b/src/label_studio_sdk/projects/exports/types/exports_convert_response.py similarity index 52% rename from src/label_studio_sdk/types/export_convert.py rename to src/label_studio_sdk/projects/exports/types/exports_convert_response.py index 2670d3fd4..1543ef9b9 100644 --- a/src/label_studio_sdk/types/export_convert.py +++ b/src/label_studio_sdk/projects/exports/types/exports_convert_response.py @@ -1,15 +1,17 @@ # This file was auto-generated by Fern from our API Definition. -from ..core.pydantic_utilities import UniversalBaseModel -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.pydantic_utilities import UniversalBaseModel import typing +from ....types.export_format import ExportFormat +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 -class ExportConvert(UniversalBaseModel): - export_type: str = pydantic.Field() +class ExportsConvertResponse(UniversalBaseModel): + export_type: typing.Optional[ExportFormat] = None + converted_format: typing.Optional[int] = pydantic.Field(default=None) """ - Export file format. + ID of the converted format """ if IS_PYDANTIC_V2: diff --git a/src/label_studio_sdk/projects/exports/types/exports_list_formats_response_item.py b/src/label_studio_sdk/projects/exports/types/exports_list_formats_response_item.py new file mode 100644 index 000000000..60bc39816 --- /dev/null +++ b/src/label_studio_sdk/projects/exports/types/exports_list_formats_response_item.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +from ....core.pydantic_utilities import UniversalBaseModel +import typing +from ....types.export_format import ExportFormat +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 + + +class ExportsListFormatsResponseItem(UniversalBaseModel): + name: typing.Optional[ExportFormat] = None + title: typing.Optional[str] = pydantic.Field(default=None) + """ + Export format title + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Export format description + """ + + link: typing.Optional[str] = pydantic.Field(default=None) + """ + Export format documentation link + """ + + tags: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Export format tags + """ + + disabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + If true, the export format is not supported by the project. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/label_studio_sdk/types/__init__.py b/src/label_studio_sdk/types/__init__.py index f4292f4df..d8ef9f777 100644 --- a/src/label_studio_sdk/types/__init__.py +++ b/src/label_studio_sdk/types/__init__.py @@ -22,9 +22,9 @@ from .data_manager_task_serializer_drafts_item import DataManagerTaskSerializerDraftsItem from .data_manager_task_serializer_predictions_item import DataManagerTaskSerializerPredictionsItem from .export import Export -from .export_convert import ExportConvert -from .export_create import ExportCreate -from .export_create_status import ExportCreateStatus +from .export_format import ExportFormat +from .export_snapshot import ExportSnapshot +from .export_snapshot_status import ExportSnapshotStatus from .export_status import ExportStatus from .file_upload import FileUpload from .filter import Filter @@ -123,9 +123,9 @@ "DataManagerTaskSerializerDraftsItem", "DataManagerTaskSerializerPredictionsItem", "Export", - "ExportConvert", - "ExportCreate", - "ExportCreateStatus", + "ExportFormat", + "ExportSnapshot", + "ExportSnapshotStatus", "ExportStatus", "FileUpload", "Filter", diff --git a/src/label_studio_sdk/types/export_create_status.py b/src/label_studio_sdk/types/export_create_status.py deleted file mode 100644 index 7a35eee4d..000000000 --- a/src/label_studio_sdk/types/export_create_status.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -ExportCreateStatus = typing.Union[typing.Literal["created", "in_progress", "failed", "completed"], typing.Any] diff --git a/src/label_studio_sdk/types/export_format.py b/src/label_studio_sdk/types/export_format.py new file mode 100644 index 000000000..02734d126 --- /dev/null +++ b/src/label_studio_sdk/types/export_format.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExportFormat = typing.Union[ + typing.Literal[ + "JSON", + "JSON_MIN", + "CSV", + "TSV", + "CONLL2003", + "COCO", + "VOC", + "BRUSH_TO_NUMPY", + "BRUSH_TO_PNG", + "ASR_MANIFEST", + "YOLO", + "YOLO_OBB", + "CSV_OLD", + "YOLO_WITH_IMAGES", + "COCO_WITH_IMAGES", + "YOLO_OBB_WITH_IMAGES", + ], + typing.Any, +] diff --git a/src/label_studio_sdk/types/export_create.py b/src/label_studio_sdk/types/export_snapshot.py similarity index 90% rename from src/label_studio_sdk/types/export_create.py rename to src/label_studio_sdk/types/export_snapshot.py index 3a691423b..4f23e9996 100644 --- a/src/label_studio_sdk/types/export_create.py +++ b/src/label_studio_sdk/types/export_snapshot.py @@ -5,7 +5,7 @@ from .user_simple import UserSimple import datetime as dt import pydantic -from .export_create_status import ExportCreateStatus +from .export_snapshot_status import ExportSnapshotStatus from .converted_format import ConvertedFormat from .task_filter_options import TaskFilterOptions from .annotation_filter_options import AnnotationFilterOptions @@ -13,7 +13,7 @@ from ..core.pydantic_utilities import IS_PYDANTIC_V2 -class ExportCreate(UniversalBaseModel): +class ExportSnapshot(UniversalBaseModel): title: typing.Optional[str] = None id: typing.Optional[int] = None created_by: typing.Optional[UserSimple] = None @@ -27,7 +27,7 @@ class ExportCreate(UniversalBaseModel): Complete or fail time """ - status: typing.Optional[ExportCreateStatus] = None + status: typing.Optional[ExportSnapshotStatus] = None md5: typing.Optional[str] = None counters: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None converted_formats: typing.Optional[typing.List[ConvertedFormat]] = None diff --git a/src/label_studio_sdk/types/export_snapshot_status.py b/src/label_studio_sdk/types/export_snapshot_status.py new file mode 100644 index 000000000..e48e1b9da --- /dev/null +++ b/src/label_studio_sdk/types/export_snapshot_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ExportSnapshotStatus = typing.Union[typing.Literal["created", "in_progress", "failed", "completed"], typing.Any] diff --git a/src/label_studio_sdk/versions/__init__.py b/src/label_studio_sdk/versions/__init__.py new file mode 100644 index 000000000..e3626239c --- /dev/null +++ b/src/label_studio_sdk/versions/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import VersionsGetResponse, VersionsGetResponseEdition + +__all__ = ["VersionsGetResponse", "VersionsGetResponseEdition"] diff --git a/src/label_studio_sdk/versions/client.py b/src/label_studio_sdk/versions/client.py new file mode 100644 index 000000000..f9d9df035 --- /dev/null +++ b/src/label_studio_sdk/versions/client.py @@ -0,0 +1,112 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.client_wrapper import SyncClientWrapper +import typing +from ..core.request_options import RequestOptions +from .types.versions_get_response import VersionsGetResponse +from ..core.pydantic_utilities import parse_obj_as +from json.decoder import JSONDecodeError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper + + +class VersionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> VersionsGetResponse: + """ + Get version information about the Label Studio instance. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VersionsGetResponse + + + Examples + -------- + from label_studio_sdk import LabelStudio + + client = LabelStudio( + api_key="YOUR_API_KEY", + ) + client.versions.get() + """ + _response = self._client_wrapper.httpx_client.request( + "api/version", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + VersionsGetResponse, + parse_obj_as( + type_=VersionsGetResponse, # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncVersionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> VersionsGetResponse: + """ + Get version information about the Label Studio instance. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VersionsGetResponse + + + Examples + -------- + import asyncio + + from label_studio_sdk import AsyncLabelStudio + + client = AsyncLabelStudio( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.versions.get() + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "api/version", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + VersionsGetResponse, + parse_obj_as( + type_=VersionsGetResponse, # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/src/label_studio_sdk/versions/types/__init__.py b/src/label_studio_sdk/versions/types/__init__.py new file mode 100644 index 000000000..852040b2d --- /dev/null +++ b/src/label_studio_sdk/versions/types/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from .versions_get_response import VersionsGetResponse +from .versions_get_response_edition import VersionsGetResponseEdition + +__all__ = ["VersionsGetResponse", "VersionsGetResponseEdition"] diff --git a/src/label_studio_sdk/versions/types/versions_get_response.py b/src/label_studio_sdk/versions/types/versions_get_response.py new file mode 100644 index 000000000..5e25f1e81 --- /dev/null +++ b/src/label_studio_sdk/versions/types/versions_get_response.py @@ -0,0 +1,73 @@ +# This file was auto-generated by Fern from our API Definition. + +from ...core.pydantic_utilities import UniversalBaseModel +import typing +import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata +from .versions_get_response_edition import VersionsGetResponseEdition +from ...core.pydantic_utilities import IS_PYDANTIC_V2 + + +class VersionsGetResponse(UniversalBaseModel): + release: typing.Optional[str] = pydantic.Field(default=None) + """ + Current release version of Label Studio + """ + + label_studio_os_package: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]], FieldMetadata(alias="label-studio-os-package") + ] = pydantic.Field(default=None) + """ + Information about the Label Studio open source package + """ + + label_studio_os_backend: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]], FieldMetadata(alias="label-studio-os-backend") + ] = pydantic.Field(default=None) + """ + Information about the Label Studio backend + """ + + label_studio_frontend: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]], FieldMetadata(alias="label-studio-frontend") + ] = pydantic.Field(default=None) + """ + Information about the Label Studio frontend + """ + + dm2: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = pydantic.Field(default=None) + """ + Information about the Data Manager 2.0 component + """ + + label_studio_converter: typing_extensions.Annotated[ + typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]], FieldMetadata(alias="label-studio-converter") + ] = pydantic.Field(default=None) + """ + Information about the Label Studio converter component + """ + + edition: typing.Optional[VersionsGetResponseEdition] = pydantic.Field(default=None) + """ + Label Studio edition (Community or Enterprise) + """ + + lsf: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = pydantic.Field(default=None) + """ + Information about the Label Studio Frontend library + """ + + backend: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = pydantic.Field(default=None) + """ + Additional backend information + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/label_studio_sdk/versions/types/versions_get_response_edition.py b/src/label_studio_sdk/versions/types/versions_get_response_edition.py new file mode 100644 index 000000000..4e8ab65e0 --- /dev/null +++ b/src/label_studio_sdk/versions/types/versions_get_response_edition.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +VersionsGetResponseEdition = typing.Union[typing.Literal["Community", "Enterprise"], typing.Any] diff --git a/tests/projects/test_exports.py b/tests/projects/test_exports.py index 1a053f7e3..42d2ad13f 100644 --- a/tests/projects/test_exports.py +++ b/tests/projects/test_exports.py @@ -7,8 +7,29 @@ async def test_list_formats(client: LabelStudio, async_client: AsyncLabelStudio) -> None: - expected_response: typing.Any = ["string"] - expected_types: typing.Tuple[typing.Any, typing.Any] = ("list", {0: None}) + expected_response: typing.Any = [ + { + "name": "JSON", + "title": "title", + "description": "description", + "link": "link", + "tags": ["tags"], + "disabled": True, + } + ] + expected_types: typing.Tuple[typing.Any, typing.Any] = ( + "list", + { + 0: { + "name": None, + "title": None, + "description": None, + "link": None, + "tags": ("list", {0: None}), + "disabled": None, + } + }, + ) response = client.projects.exports.list_formats(id=1) validate_response(response, expected_response, expected_types) @@ -52,10 +73,10 @@ async def test_list_(client: LabelStudio, async_client: AsyncLabelStudio) -> Non } }, ) - response = client.projects.exports.list(id=1) + response = client.projects.exports.list(project_id=1) validate_response(response, expected_response, expected_types) - async_response = await async_client.projects.exports.list(id=1) + async_response = await async_client.projects.exports.list(project_id=1) validate_response(async_response, expected_response, expected_types) @@ -118,10 +139,10 @@ async def test_create(client: LabelStudio, async_client: AsyncLabelStudio) -> No "interpolate_key_frames": None, }, } - response = client.projects.exports.create(id_=1) + response = client.projects.exports.create(project_id=1) validate_response(response, expected_response, expected_types) - async_response = await async_client.projects.exports.create(id_=1) + async_response = await async_client.projects.exports.create(project_id=1) validate_response(async_response, expected_response, expected_types) @@ -154,44 +175,31 @@ async def test_get(client: LabelStudio, async_client: AsyncLabelStudio) -> None: "counters": ("dict", {0: (None, None)}), "converted_formats": ("list", {0: {"id": "integer", "status": None, "export_type": None, "traceback": None}}), } - response = client.projects.exports.get(id=1, export_pk="export_pk") + response = client.projects.exports.get(project_id=1, export_pk="export_pk") validate_response(response, expected_response, expected_types) - async_response = await async_client.projects.exports.get(id=1, export_pk="export_pk") + async_response = await async_client.projects.exports.get(project_id=1, export_pk="export_pk") validate_response(async_response, expected_response, expected_types) async def test_delete(client: LabelStudio, async_client: AsyncLabelStudio) -> None: # Type ignore to avoid mypy complaining about the function not being meant to return a value assert ( - client.projects.exports.delete(id=1, export_pk="export_pk") # type: ignore[func-returns-value] + client.projects.exports.delete(project_id=1, export_pk="export_pk") # type: ignore[func-returns-value] is None ) assert ( - await async_client.projects.exports.delete(id=1, export_pk="export_pk") # type: ignore[func-returns-value] + await async_client.projects.exports.delete(project_id=1, export_pk="export_pk") # type: ignore[func-returns-value] is None ) async def test_convert(client: LabelStudio, async_client: AsyncLabelStudio) -> None: - expected_response: typing.Any = {"export_type": "export_type"} - expected_types: typing.Any = {"export_type": None} - response = client.projects.exports.convert(id=1, export_pk="export_pk", export_type="export_type") + expected_response: typing.Any = {"export_type": "JSON", "converted_format": 1} + expected_types: typing.Any = {"export_type": None, "converted_format": "integer"} + response = client.projects.exports.convert(project_id=1, export_pk="export_pk") validate_response(response, expected_response, expected_types) - async_response = await async_client.projects.exports.convert(id=1, export_pk="export_pk", export_type="export_type") + async_response = await async_client.projects.exports.convert(project_id=1, export_pk="export_pk") validate_response(async_response, expected_response, expected_types) - - -async def test_download(client: LabelStudio, async_client: AsyncLabelStudio) -> None: - # Type ignore to avoid mypy complaining about the function not being meant to return a value - assert ( - client.projects.exports.download(id=1, export_pk="export_pk") # type: ignore[func-returns-value] - is None - ) - - assert ( - await async_client.projects.exports.download(id=1, export_pk="export_pk") # type: ignore[func-returns-value] - is None - ) diff --git a/tests/test_versions.py b/tests/test_versions.py new file mode 100644 index 000000000..16e3f4d37 --- /dev/null +++ b/tests/test_versions.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +from label_studio_sdk import LabelStudio +from label_studio_sdk import AsyncLabelStudio +import typing +from .utilities import validate_response + + +async def test_get(client: LabelStudio, async_client: AsyncLabelStudio) -> None: + expected_response: typing.Any = { + "release": "release", + "label-studio-os-package": {"key": "value"}, + "label-studio-os-backend": {"key": "value"}, + "label-studio-frontend": {"key": "value"}, + "dm2": {"key": "value"}, + "label-studio-converter": {"key": "value"}, + "edition": "Community", + "lsf": {"key": "value"}, + "backend": {"key": "value"}, + } + expected_types: typing.Any = { + "release": None, + "label-studio-os-package": ("dict", {0: (None, None)}), + "label-studio-os-backend": ("dict", {0: (None, None)}), + "label-studio-frontend": ("dict", {0: (None, None)}), + "dm2": ("dict", {0: (None, None)}), + "label-studio-converter": ("dict", {0: (None, None)}), + "edition": None, + "lsf": ("dict", {0: (None, None)}), + "backend": ("dict", {0: (None, None)}), + } + response = client.versions.get() + validate_response(response, expected_response, expected_types) + + async_response = await async_client.versions.get() + validate_response(async_response, expected_response, expected_types)