Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/python-id-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@e2b/python-id': minor
---

Initial release: prefixed, human-legible IDs for E2B resources (`prj_…`, `wrk_…`, `sec_…`, …) in the TypeID encoding the Go services use, as a standard-library-only package (`e2b-id` on PyPI).
1 change: 1 addition & 0 deletions .github/scripts/build_release_itinerary.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const labels = {
'@e2b/dockerfile-utils': 'Dockerfile Utils JS (@e2b/dockerfile-utils)',
'@e2b/dockerfile-utils-python': 'Dockerfile Utils Python (e2b-dockerfile-utils)',
'@e2b/id': 'ID JS (@e2b/id)',
'@e2b/python-id': 'ID Python (e2b-id)',
}

const statusFile = process.argv[2]
Expand Down
29 changes: 22 additions & 7 deletions .github/workflows/id_tests.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
name: Test ID Packages

# `@e2b/id` is a pure codec with no network and no API key, so unlike the SDK
# suites this one needs no secrets and no staging leg.
# `@e2b/id` and `e2b_id` are pure codecs with no network and no API key, so
# unlike the SDK suites this one needs no secrets and no staging leg. Both run in
# one job because their point is that they agree: the two suites pin the same
# golden vectors and the same corpus digest.
on:
workflow_call:

Expand Down Expand Up @@ -49,18 +51,31 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

# The interop suite drives python3 to prove the format really is
# "b32encode, lowercase, strip padding, rotate", so an interpreter has to
# be on PATH even though the package itself has no Python component.
- name: Setup Python
uses: actions/setup-python@v5
# Also gives js-id's interop suite the interpreter it drives: an
# independent arithmetic encoder has to agree over the whole corpus.
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
version: '${{ env.TOOL_VERSION_UV }}'
python-version: '${{ env.TOOL_VERSION_PYTHON }}'
enable-cache: true

- name: Install Python dependencies
working-directory: packages/python-id
run: uv sync --locked

- name: Test build (JS)
working-directory: packages/js-id
run: pnpm build

- name: Test build (Python)
working-directory: packages/python-id
run: uv build

- name: Run tests (JS)
working-directory: packages/js-id
run: pnpm test

- name: Run tests (Python)
working-directory: packages/python-id
run: uv run pytest --verbose
5 changes: 5 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ jobs:
run: |
uv sync --locked

- name: Install Python dependencies (id)
working-directory: packages/python-id
run: |
uv sync --locked

- name: Run linting
run: |
pnpm run lint
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/pkg_artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,19 @@ jobs:
name: e2b-dockerfile-utils-python
path: packages/dockerfile-utils-python/dist/*

- name: Build ID Python
working-directory: packages/python-id
run: |
BASE_VERSION=$(uv version --short)
uv version "${BASE_VERSION}+${BRANCH_ID}"
uv build

- name: Upload ID Python artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: e2b-id-python
path: packages/python-id/dist/*

- name: Build Python SDK
working-directory: packages/python-sdk
run: |
Expand Down Expand Up @@ -250,6 +263,8 @@ jobs:
CODE_INTERPRETER_PY_WHL=$(artifact_basename packages/code-interpreter-python/dist/*.whl)
DESKTOP_PY_VERSION=$(grep '^version' packages/desktop-python/pyproject.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
DESKTOP_PY_WHL=$(artifact_basename packages/desktop-python/dist/*.whl)
ID_PY_VERSION=$(grep '^version' packages/python-id/pyproject.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
ID_PY_WHL=$(artifact_basename packages/python-id/dist/*.whl)

BODY="<!-- e2b-pkg-artifacts -->"$'\n'
BODY+="### Package Artifacts"$'\n\n'
Expand Down Expand Up @@ -285,6 +300,10 @@ jobs:
BODY+="**Desktop Python SDK** (\`e2b-desktop==${DESKTOP_PY_VERSION}\`):"$'\n'
BODY+='```sh'$'\n'
BODY+="pip install ./${DESKTOP_PY_WHL}"$'\n'
BODY+='```'$'\n\n'
BODY+="**ID Python** (\`e2b-id==${ID_PY_VERSION}\`):"$'\n'
BODY+='```sh'$'\n'
BODY+="pip install ./${ID_PY_WHL}"$'\n'
BODY+='```'$'\n'

COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
dockerfile_utils_js: ${{ steps.dockerfile_utils_js.outputs.release }}
dockerfile_utils_python: ${{ steps.dockerfile_utils_python.outputs.release }}
id_js: ${{ steps.id_js.outputs.release }}
id_python: ${{ steps.id_python.outputs.release }}
itinerary: ${{ steps.itinerary.outputs.itinerary }}
steps:
- name: Check the ref
Expand Down Expand Up @@ -156,6 +157,13 @@ jobs:
IS_RELEASE=$(./.github/scripts/is_release_for_package.sh "@e2b/id" "$RUNNER_TEMP/release-plan.json")
echo "release=$IS_RELEASE" >> "$GITHUB_OUTPUT"

- name: Check ID Python Release
id: id_python
if: steps.version.outputs.release == 'true'
run: |
IS_RELEASE=$(./.github/scripts/is_release_for_package.sh "@e2b/python-id" "$RUNNER_TEMP/release-plan.json")
echo "release=$IS_RELEASE" >> "$GITHUB_OUTPUT"

- name: Build release itinerary
id: itinerary
if: steps.version.outputs.release == 'true'
Expand Down Expand Up @@ -234,7 +242,8 @@ jobs:
id-tests:
name: ID Package Tests
needs: [preflight]
if: needs.preflight.outputs.id_js == 'true'
# One workflow tests both ID packages, since their point is that they agree.
if: needs.preflight.outputs.id_js == 'true' || needs.preflight.outputs.id_python == 'true'
uses: ./.github/workflows/id_tests.yml

publish:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/sdk_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ jobs:
- *shared
- '.github/workflows/id_tests.yml'
- 'packages/js-id/**/!(*.md)'
- 'packages/python-id/**/!(*.md)'

js-tests:
name: JS SDK Tests
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ jobs:
run: |
uv sync --locked

- name: Install Python dependencies (id)
working-directory: packages/python-id
run: |
uv sync --locked

- name: Run typecheck
run: |
pnpm run typecheck
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
.ruff_cache/
cover/

# Translations
Expand Down
15 changes: 8 additions & 7 deletions packages/js-id/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ isId('project', 'vol_01kyjhkfy5f16s71n5tymkmt9a')
// false
```

The format is deliberately portable: any TypeID library reads it, and so does
any language with integers. A Python port lands alongside this one.
[`e2b_id`](../python-id) is the same format in Python, name for name — its
README lists [the one place they differ](../python-id/README.md#where-this-differs-from-e2bid)
(Python moves `uuid.UUID` objects where JS moves hex strings).

## The format

Expand Down Expand Up @@ -146,8 +147,8 @@ pnpm test
pnpm lint && pnpm typecheck
```

`tests/vectors.ts` holds the golden encodings (the Go suite's among them), a
seeded 1306-value corpus and a `CORPUS_DIGEST` over it. Every port of this format
pins the same three, so a change to the format on one side alone cannot pass on
the other. `pnpm test` also pipes the whole corpus through `python3` to check the
snippet above.
`tests/vectors.ts` and `packages/python-id/tests/vectors.py` are the same file in
two languages: the same golden encodings (the Go suite's among them), the same
seeded corpus, and the same `CORPUS_DIGEST` over it. Changing the format on one
side alone fails on both. `pnpm test` also pipes the whole corpus through
`python3` to check the snippet above.
4 changes: 2 additions & 2 deletions packages/js-id/tests/codec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ describe('the format', () => {
}
})

// The one constant every port holds: same corpus, same format, same digest.
test('the corpus digest is the cross-implementation contract', () => {
// The one constant both suites hold: same corpus, same format, same digest.
test('the corpus is the same corpus e2b_id checks', () => {
expect(corpusDigest(encodeBytes)).toBe(CORPUS_DIGEST)
})

Expand Down
12 changes: 6 additions & 6 deletions packages/js-id/tests/interop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import { corpus } from './vectors'
* The snippet below is the one in `src/codec.ts`'s documentation and in
* `@e2b/id`'s README, verbatim: the alphabet and integer arithmetic, no bit
* shuffling. If this passes, the format really is "the 128-bit value in 26
* Crockford base32 digits, most significant first", so any port tested the
* same way against the same corpus decodes what this package emits.
* `int.to_bytes(16)` raising on 129 bits is also Python's own statement of the
* leading-digit rule.
* Crockford base32 digits, most significant first", and `e2b_id` — which is
* tested the same way against the same corpus — decodes what this package
* emits. `int.to_bytes(16)` raising on 129 bits is also Python's own statement
* of the leading-digit rule.
*/
const SNIPPET = `
import sys
Expand Down Expand Up @@ -73,8 +73,8 @@ describe.skipIf(process.env.E2B_ID_SKIP_INTEROP === '1')(
() => {
test('in both directions, over the whole corpus', () => {
// Not a skip: on CI this suite is the only thing standing between a
// format change and a silent fork from the other ports, so a missing
// interpreter has to be loud.
// format change and a silent fork from e2b_id, so a missing interpreter
// has to be loud.
expect(
python,
'no python3 on PATH; set E2B_ID_SKIP_INTEROP=1 to skip this suite deliberately'
Expand Down
14 changes: 7 additions & 7 deletions packages/js-id/tests/vectors.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/**
* The shared test material: golden encodings, and a deterministic corpus.
*
* Every port of this format carries this file value for value — the same
* goldens, the same LCG, the same corpus in the same order. That is what keeps
* implementations from drifting: each checks the same inputs against the same
* fixed outputs and against its own independent reference encoder, so agreeing
* with this file means agreeing with each other.
* `packages/python-id/tests/vectors.py` is the same file in Python, value for
* value — the same goldens, the same LCG, the same corpus in the same order.
* That is what keeps the two implementations from drifting: both check the same
* inputs against the same fixed outputs and against their own independent
* reference encoder, so agreeing with this file means agreeing with each other.
*/

import { createHash } from 'node:crypto'
Expand Down Expand Up @@ -101,8 +101,8 @@ export const GOLDEN_IDS: ReadonlyArray<

/**
* sha256 of every encoding in {@link corpus}, newline-joined, in order. The same
* constant is pinned by every port; if two implementations ever disagree about
* the corpus or the format, one of them fails on it.
* constant appears in `vectors.py`; if the two implementations ever disagree
* about the corpus or the format, one of them fails on it.
*/
export const CORPUS_DIGEST =
'e58522dfc30f44687edca477cc58f37015d7c9e1c46311a354654db07b2f6bdc'
Expand Down
Loading
Loading