diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2cb68f97..f2378204 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ "master", "dev" ] + branches: [ "master" ] pull_request: - branches: [ "master", "dev" ] + branches: [ "master" ] jobs: # check python formatting @@ -27,6 +27,20 @@ jobs: with: clang-format-version: '20' + # if it was a push to master, update latest + docs: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' }} + steps: + - uses: actions/checkout@v4 + name: Checkout + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Install the project + run: uv sync --dev --verbose + - name: Deploy docs + run: mike deploy -p -u latest + # run tests on both mac and ubuntu, across all python versions test: runs-on: ${{ matrix.os }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e4a7fd88..81b73523 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,8 @@ on: jobs: build_wheels: runs-on: ${{ matrix.os }} + env: + VCPKG_DEFAULT_BINARY_CACHE: "${{ github.workspace }}/.vcpkg_cache" strategy: matrix: os: [ubuntu-latest, macos-13, macos-14] @@ -17,12 +19,14 @@ jobs: name: Checkout # Let vcpkg store caches in github actions - - name: Export GitHub Actions cache environment variables - uses: actions/github-script@v7 + - name: Restore vcpkg cache + id: cache + uses: actions/cache/restore@v4 with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + path: "${{ env.VCPKG_DEFAULT_BINARY_CACHE }}" + key: vcpkg-${{ matrix.os }} + - name: Ensure vcpkg cache directory exists + run: mkdir -p "${{ env.VCPKG_DEFAULT_BINARY_CACHE }}" # Do all the building - run: ./cpp/setup_pipelines.sh @@ -31,12 +35,24 @@ jobs: name: Build wheel env: # for vcpkg caches - CIBW_ENVIRONMENT: > - VCPKG_BINARY_SOURCES="clear;x-gha,readwrite" - ACTIONS_CACHE_URL="$ACTIONS_CACHE_URL" - ACTIONS_RUNTIME_TOKEN="$ACTIONS_RUNTIME_TOKEN" - MACOSX_DEPLOYMENT_TARGET="11" - CIBW_ENVIRONMENT_PASS_LINUX: VCPKG_BINARY_SOURCES ACTIONS_CACHE_URL ACTIONS_RUNTIME_TOKEN + CIBW_ENVIRONMENT_LINUX : VCPKG_DEFAULT_BINARY_CACHE="/host${{ env.VCPKG_DEFAULT_BINARY_CACHE }}" + + # Delete the old cache on hit to emulate a cache update. See + # https://github.com/actions/cache/issues/342. + - name: Delete old cache + env: + GH_TOKEN: ${{ github.token }} + if: steps.cache.outputs.cache-hit + # Using `--repo` makes it so that this step doesn't require checking out the repo first. + run: gh cache delete --repo ${{ github.repository }} ${{ steps.cache.outputs.cache-primary-key }} + + # cache vcpkg + - name: Save vcpkg cache + if: always() + uses: actions/cache/save@v4 + with: + path: "${{ env.VCPKG_DEFAULT_BINARY_CACHE }}" + key: vcpkg-${{ matrix.os }} # upload to use in other jobs - uses: actions/upload-artifact@v4 @@ -80,4 +96,18 @@ jobs: uses: softprops/action-gh-release@v2 with: generate_release_notes: true - files: dist/* \ No newline at end of file + files: dist/* + + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + name: Checkout + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Install the project + run: uv sync --dev --verbose + - name: Get version + run: echo "EVALIO_VERSION=$(python -c 'import evalio; print(evalio.__version__)')" >> $GITHUB_ENV + - name: Deploy docs + run: mike deploy -p -u ${{ env.EVALIO_VERSION }} stable \ No newline at end of file diff --git a/.gitignore b/.gitignore index edcd6b0f..eb580cb3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,12 @@ wheelhouse .vcpkg_installed .vcpkg .vcpkg_cache +site # other caches .mypy_cache .ruff_cache +.pytest_cache .vscode .venv __pycache__ diff --git a/cpp/bindings/pipeline.h b/cpp/bindings/pipeline.h index d8240235..3ee25c23 100644 --- a/cpp/bindings/pipeline.h +++ b/cpp/bindings/pipeline.h @@ -50,20 +50,35 @@ class PyPipeline : public evalio::Pipeline { inline void makeBasePipeline(nb::module_ &m) { nb::class_(m, "Pipeline") - .def(nb::init<>()) - .def_static("name", &evalio::Pipeline::name) - .def_static("default_params", &evalio::Pipeline::default_params) - .def_static("url", &evalio::Pipeline::url) - .def_static("version", &evalio::Pipeline::version) - .def("pose", &evalio::Pipeline::pose) - .def("map", &evalio::Pipeline::map) - .def("initialize", &evalio::Pipeline::initialize) - .def("add_imu", &evalio::Pipeline::add_imu, "mm"_a) - .def("add_lidar", &evalio::Pipeline::add_lidar, "mm"_a) - .def("set_params", &evalio::Pipeline::set_params, "params"_a) - .def("set_imu_params", &evalio::Pipeline::set_imu_params, "params"_a) - .def("set_lidar_params", &evalio::Pipeline::set_lidar_params, "params"_a) - .def("set_imu_T_lidar", &evalio::Pipeline::set_imu_T_lidar, "T"_a); + .def(nb::init<>(), "Construct a new pipeline.") + .def_static("name", &evalio::Pipeline::name, "Name of the pipeline.") + .def_static("default_params", &evalio::Pipeline::default_params, + "Default parameters for the pipeline.") + .def_static("url", &evalio::Pipeline::url, + "URL for more information about the pipeline.") + .def_static("version", &evalio::Pipeline::version, + "Version of the pipeline.") + .def("pose", &evalio::Pipeline::pose, "Most recent pose estimate.") + .def("map", &evalio::Pipeline::map, "Map of the environment.") + .def("initialize", &evalio::Pipeline::initialize, + "Initialize the pipeline. Must be called after constructing the " + "object and before setting parameters.") + .def("add_imu", &evalio::Pipeline::add_imu, "mm"_a, + "Register an IMU measurement.") + .def("add_lidar", &evalio::Pipeline::add_lidar, "mm"_a, + "Register a LiDAR measurement.") + .def("set_params", &evalio::Pipeline::set_params, "params"_a, + "Set parameters for the pipeline. This will override any default " + "parameters.") + .def("set_imu_params", &evalio::Pipeline::set_imu_params, "params"_a, + "Set IMU parameters for the pipeline.") + .def("set_lidar_params", &evalio::Pipeline::set_lidar_params, "params"_a, + "Set LiDAR parameters for the pipeline.") + .def("set_imu_T_lidar", &evalio::Pipeline::set_imu_T_lidar, "T"_a, + "Set the transformation from IMU to LiDAR frame.") + .doc() = "Base class for all pipelines. This class defines the interface " + "for interacting with pipelines, and is intended to be " + "subclassed by specific implementations."; } } // namespace evalio \ No newline at end of file diff --git a/cpp/bindings/pipelines/bindings.h b/cpp/bindings/pipelines/bindings.h index 6874f4c0..81dc8d1f 100644 --- a/cpp/bindings/pipelines/bindings.h +++ b/cpp/bindings/pipelines/bindings.h @@ -24,8 +24,11 @@ inline void makePipelines(nb::module_ &m) { .def_static("name", &KissICP::name) .def_static("default_params", &KissICP::default_params) .def_static("url", &KissICP::url) - .def_static("version", &KissICP::version); - + .def_static("version", &KissICP::version) + .doc() = + "KissICP LiDAR-only pipeline for point cloud registration. KissICP is " + "designed to be simple and easy to use, while still providing good " + "performance with minimal parameter tuning required across datasets."; #endif #ifdef EVALIO_LIO_SAM @@ -34,7 +37,11 @@ inline void makePipelines(nb::module_ &m) { .def_static("name", &LioSam::name) .def_static("default_params", &LioSam::default_params) .def_static("url", &LioSam::url) - .def_static("version", &LioSam::version); + .def_static("version", &LioSam::version) + .doc() = + "Lidar-Inertial Smoothing and Mapping (LioSAM) pipeline. LioSAM is an " + "extension of LOAM (=> uses planar and edge features) that additionally " + "utilizes an IMU for initializing ICP steps and for dewarping points"; #endif } } // namespace evalio \ No newline at end of file diff --git a/cpp/bindings/types.h b/cpp/bindings/types.h index 8bbd435d..ec5940b2 100644 --- a/cpp/bindings/types.h +++ b/cpp/bindings/types.h @@ -18,17 +18,19 @@ namespace evalio { inline void makeTypes(nb::module_ &m) { nb::class_(m, "Duration") - .def_static("from_sec", &Duration::from_sec) - .def_static("from_nsec", &Duration::from_nsec) - .def("to_sec", &Duration::to_sec) - .def("to_nsec", &Duration::to_nsec) - .def_ro("nsec", &Duration::nsec) - .def(nb::self < nb::self) - .def(nb::self > nb::self) - .def(nb::self == nb::self) - .def(nb::self != nb::self) - .def(nb::self - nb::self) - .def(nb::self + nb::self) + .def_static("from_sec", &Duration::from_sec, "sec"_a, + "Create a Duration from seconds") + .def_static("from_nsec", &Duration::from_nsec, "nsec"_a, + "Create a Duration from nanoseconds") + .def("to_sec", &Duration::to_sec, "Convert to seconds") + .def("to_nsec", &Duration::to_nsec, "Convert to nanoseconds") + .def_ro("nsec", &Duration::nsec, "Underlying nanoseconds representation") + .def(nb::self < nb::self, "Compare two Durations") + .def(nb::self > nb::self, "Compare two Durations") + .def(nb::self == nb::self, "Check for equality") + .def(nb::self != nb::self, "Check for inequality") + .def(nb::self - nb::self, "Compute the difference between two Durations") + .def(nb::self + nb::self, "Add two Durations") .def("__repr__", &Duration::toString) .def("__copy__", [](const Duration &self) { return Duration(self); }) .def( @@ -37,25 +39,35 @@ inline void makeTypes(nb::module_ &m) { "memo"_a) .def("__getstate__", [](const Duration &p) { return nb::make_tuple(p.nsec); }) - .def("__setstate__", [](Duration &p, std::tuple t) { - new (&p) Duration{.nsec = std::get<0>(t)}; - }); + .def("__setstate__", + [](Duration &p, std::tuple t) { + new (&p) Duration{.nsec = std::get<0>(t)}; + }) + .doc() = + "Duration class for representing a positive or negative delta time, uses " + "int64 as the underlying data storage for nanoseconds."; nb::class_(m, "Stamp") - .def(nb::init(), nb::kw_only(), "sec"_a, "nsec"_a) - .def_static("from_sec", &Stamp::from_sec) - .def_static("from_nsec", &Stamp::from_nsec) - .def("to_sec", &Stamp::to_sec) - .def("to_nsec", &Stamp::to_nsec) - .def_ro("sec", &Stamp::sec) - .def_ro("nsec", &Stamp::nsec) - .def(nb::self < nb::self) - .def(nb::self > nb::self) - .def(nb::self == nb::self) - .def(nb::self != nb::self) - .def(nb::self - nb::self) - .def(nb::self + Duration()) - .def(nb::self - Duration()) + .def(nb::init(), nb::kw_only(), "sec"_a, "nsec"_a, + "Create a Stamp from seconds and nanoseconds") + .def_static("from_sec", &Stamp::from_sec, "sec"_a, + "Create a Stamp from seconds") + .def_static("from_nsec", &Stamp::from_nsec, "nsec"_a, + "Create a Stamp from nanoseconds") + .def("to_sec", &Stamp::to_sec, "Convert to seconds") + .def("to_nsec", &Stamp::to_nsec, "Convert to nanoseconds") + .def_ro("sec", &Stamp::sec, "Underlying seconds storage") + .def_ro("nsec", &Stamp::nsec, "Underlying nanoseconds storage") + .def(nb::self < nb::self, + "Compare two Stamps to see which happened first") + .def(nb::self > nb::self, + "Compare two Stamps to see which happened first") + .def(nb::self == nb::self, "Check for equality") + .def(nb::self != nb::self, "Check for equality") + .def(nb::self - nb::self, + "Compute the difference between two Stamps, returning a duration") + .def(nb::self + Duration(), "Add a Duration to a Stamp") + .def(nb::self - Duration(), "Subtract a Duration from a Stamp") .def("__repr__", &Stamp::toString) .def("__copy__", [](const Stamp &self) { return Stamp(self); }) .def( @@ -63,9 +75,13 @@ inline void makeTypes(nb::module_ &m) { [](const Stamp &self, nb::dict) { return Stamp(self); }, "memo"_a) .def("__getstate__", [](const Stamp &p) { return nb::make_tuple(p.sec, p.nsec); }) - .def("__setstate__", [](Stamp &p, std::tuple t) { - new (&p) Stamp{.sec = std::get<0>(t), .nsec = std::get<1>(t)}; - }); + .def("__setstate__", + [](Stamp &p, std::tuple t) { + new (&p) Stamp{.sec = std::get<0>(t), .nsec = std::get<1>(t)}; + }) + .doc() = + "Stamp class for representing an absolute point in time, uses uint32 as " + "the underlying data storage for seconds and nanoseconds."; ; // Lidar @@ -74,17 +90,24 @@ inline void makeTypes(nb::module_ &m) { uint16_t>(), nb::kw_only(), "x"_a = 0, "y"_a = 0, "z"_a = 0, "intensity"_a = 0, "t"_a = Duration::from_sec(0.0), "range"_a = 0, "row"_a = 0, - "col"_a = 0) - .def_rw("x", &Point::x) - .def_rw("y", &Point::y) - .def_rw("z", &Point::z) - .def_rw("intensity", &Point::intensity) - .def_rw("range", &Point::range) - .def_rw("t", &Point::t) - .def_rw("row", &Point::row) - .def_rw("col", &Point::col) - .def(nb::self == nb::self) - .def(nb::self != nb::self) + "col"_a = 0, + "Create a Point from x, y, z, intensity, t, range, row, col") + .def_rw("x", &Point::x, "X coordinate") + .def_rw("y", &Point::y, "Y coordinate") + .def_rw("z", &Point::z, "Z coordinate") + .def_rw("intensity", &Point::intensity, "Intensity value as a float.") + .def_rw("range", &Point::range, "Range value as a uint32.") + .def_rw("t", &Point::t, + "Timestamp of the point as a Duration. In evalio, this is always " + "relative to the point cloud stamp, which occurs at the start of " + "the scan.") + .def_rw("row", &Point::row, + "Row index of the point in the point cloud. Also known as the " + "scanline index.") + .def_rw("col", &Point::col, + "Column index of the point in the point cloud.") + .def(nb::self == nb::self, "Check for equality") + .def(nb::self != nb::self, "Check for inequality") .def("__repr__", &Point::toString) .def("__getstate__", [](const Point &p) { @@ -103,17 +126,24 @@ inline void makeTypes(nb::module_ &m) { .range = std::get<5>(t), .row = std::get<6>(t), .col = std::get<7>(t)}; - }); + }) + .doc() = "Point is a general point structure in evalio, with common " + "point cloud attributes included."; nb::class_(m, "LidarMeasurement") .def(nb::init(), "stamp"_a) .def(nb::init>(), "stamp"_a, "points"_a) - .def_rw("stamp", &LidarMeasurement::stamp) - .def_rw("points", &LidarMeasurement::points) - .def("to_vec_positions", &LidarMeasurement::to_vec_positions) - .def("to_vec_stamps", &LidarMeasurement::to_vec_stamps) - .def(nb::self == nb::self) - .def(nb::self != nb::self) + .def_rw("stamp", &LidarMeasurement::stamp, + "Timestamp of the point cloud, always at the start of the scan.") + .def_rw("points", &LidarMeasurement::points, + "List of points in the " + "point cloud. Note, this is always in row major format.") + .def("to_vec_positions", &LidarMeasurement::to_vec_positions, + "Convert the point cloud to a (n,3) numpy array.") + .def("to_vec_stamps", &LidarMeasurement::to_vec_stamps, + "Convert the point stamps to a list of durations.") + .def(nb::self == nb::self, "Check for equality") + .def(nb::self != nb::self, "Check for inequality") .def("__repr__", &LidarMeasurement::toString) .def("__getstate__", [](const LidarMeasurement &p) { @@ -122,32 +152,48 @@ inline void makeTypes(nb::module_ &m) { .def("__setstate__", [](LidarMeasurement &p, std::tuple> t) { new (&p) LidarMeasurement(std::get<0>(t), std::get<1>(t)); - }); + }) + .doc() = "LidarMeasurement is a structure for storing a point cloud " + "measurement, with a timestamp and a vector of points. Note, " + "the stamp always represents the _start_ of the scan. " + "Additionally, the points are always in row major format."; nb::class_(m, "LidarParams") .def(nb::init(), nb::kw_only(), "num_rows"_a, "num_columns"_a, "min_range"_a, "max_range"_a, "rate"_a = 10.0, "brand"_a = "-", "model"_a = "-") - .def_ro("num_rows", &LidarParams::num_rows) - .def_ro("num_columns", &LidarParams::num_columns) - .def_ro("min_range", &LidarParams::min_range) - .def_ro("max_range", &LidarParams::max_range) - .def_ro("rate", &LidarParams::rate) - .def_ro("brand", &LidarParams::brand) - .def_ro("model", &LidarParams::model) - .def("delta_time", &LidarParams::delta_time) - .def("__repr__", &LidarParams::toString); + .def_ro("num_rows", &LidarParams::num_rows, + "Number of rows in the point cloud, also known as the scanlines.") + .def_ro("num_columns", &LidarParams::num_columns, + "Number of columns in the point cloud, also known as the number " + "of points per scanline.") + .def_ro("min_range", &LidarParams::min_range, + "Minimum range of the lidar sensor, in meters.") + .def_ro("max_range", &LidarParams::max_range, + "Maximum range of the lidar sensor, in meters.") + .def_ro("rate", &LidarParams::rate, "Rate of the lidar sensor, in Hz.") + .def_ro("brand", &LidarParams::brand, "Brand of the lidar sensor.") + .def_ro("model", &LidarParams::model, "Model of the lidar sensor.") + .def("delta_time", &LidarParams::delta_time, + "Get the time between two consecutive scans as a Duration. Inverse " + "of the rate.") + .def("__repr__", &LidarParams::toString) + .doc() = "LidarParams is a structure for storing the parameters of a " + "lidar sensor."; // Imu nb::class_(m, "ImuMeasurement") .def(nb::init(), "stamp"_a, "gyro"_a, "accel"_a) - .def_ro("stamp", &ImuMeasurement::stamp) - .def_ro("gyro", &ImuMeasurement::gyro) - .def_ro("accel", &ImuMeasurement::accel) - .def(nb::self == nb::self) - .def(nb::self != nb::self) + .def_ro("stamp", &ImuMeasurement::stamp, + "Timestamp of the IMU measurement.") + .def_ro("gyro", &ImuMeasurement::gyro, + "Gyroscope measurement as a 3D vector.") + .def_ro("accel", &ImuMeasurement::accel, + "Accelerometer measurement as a 3D vector.") + .def(nb::self == nb::self, "Check for equality") + .def(nb::self != nb::self, "Check for inequality") .def("__repr__", &ImuMeasurement::toString) .def("__getstate__", [](const ImuMeasurement &p) { @@ -159,7 +205,10 @@ inline void makeTypes(nb::module_ &m) { new (&p) ImuMeasurement{.stamp = std::get<0>(t), .gyro = std::get<1>(t), .accel = std::get<2>(t)}; - }); + }) + + .doc() = + "ImuMeasurement is a simple structure for storing an IMU measurement."; nb::class_(m, "ImuParams") .def(nb::init(m, "SO3") .def(nb::init(), nb::kw_only(), "qx"_a, "qy"_a, "qz"_a, "qw"_a) - .def_ro("qx", &SO3::qx) - .def_ro("qy", &SO3::qy) - .def_ro("qz", &SO3::qz) - .def_ro("qw", &SO3::qw) - .def_static("identity", &SO3::identity) - .def_static("fromMat", &SO3::fromMat) - .def_static("exp", &SO3::exp) - .def("inverse", &SO3::inverse) - .def("log", &SO3::log) - .def("toMat", &SO3::toMat) - .def("rotate", &SO3::rotate) - .def(nb::self * nb::self) - .def(nb::self == nb::self) - .def(nb::self != nb::self) + .def_ro("qx", &SO3::qx, "X component of the quaternion.") + .def_ro("qy", &SO3::qy, "Y component of the quaternion.") + .def_ro("qz", &SO3::qz, "Z component of the quaternion.") + .def_ro("qw", &SO3::qw, "Scalar component of the quaternion.") + .def_static("identity", &SO3::identity, "Create an identity rotation.") + .def_static("fromMat", &SO3::fromMat, "mat"_a, + "Create a rotation from a 3x3 rotation matrix.") + .def_static("exp", &SO3::exp, "v"_a, + "Create a rotation from a 3D vector.") + .def("inverse", &SO3::inverse, "Compute the inverse of the rotation.") + .def("log", &SO3::log, "Compute the logarithm of the rotation.") + .def("toMat", &SO3::toMat, "Convert the rotation to a 3x3 matrix.") + .def("rotate", &SO3::rotate, "v"_a, "Rotate a 3D vector by the rotation.") + .def(nb::self * nb::self, "Compose two rotations.") + .def(nb::self == nb::self, "Check for equality") + .def(nb::self != nb::self, "Check for inequality") .def("__repr__", &SO3::toString) .def("__copy__", [](const SO3 &self) { return SO3(self); }) .def( "__deepcopy__", [](const SO3 &self, nb::dict) { return SO3(self); }, - "memo"_a); + "memo"_a) + .def("__getstate__", + [](const SO3 &p) { return nb::make_tuple(p.qx, p.qy, p.qz, p.qw); }) + .def("__setstate__", + [](SO3 &p, std::tuple t) { + new (&p) SO3{.qx = std::get<0>(t), + .qy = std::get<1>(t), + .qz = std::get<2>(t), + .qw = std::get<3>(t)}; + }) + .doc() = "SO3 class for representing a 3D rotation using a quaternion. " + "This is outfitted with some basic functionality, but mostly " + "intended for storage and converting between types."; nb::class_(m, "SE3") - .def(nb::init(), "rot"_a, "trans"_a) - .def_static("identity", &SE3::identity) - .def_static("fromMat", &SE3::fromMat) - .def_ro("rot", &SE3::rot) - .def_ro("trans", &SE3::trans) - .def("toMat", &SE3::toMat) - .def("inverse", &SE3::inverse) - .def(nb::self * nb::self) - .def(nb::self == nb::self) - .def(nb::self != nb::self) + .def(nb::init(), "rot"_a, "trans"_a, + "Create a SE3 from a rotation and translation.") + .def_static("identity", &SE3::identity, "Create an identity SE3.") + .def_static("fromMat", &SE3::fromMat, "mat"_a, + "Create a SE3 from a 4x4 transformation matrix.") + .def_ro("rot", &SE3::rot, "Rotation as a SO3 object.") + .def_ro("trans", &SE3::trans, "Translation as a 3D vector.") + .def("toMat", &SE3::toMat, "Convert to a 4x4 matrix.") + .def("inverse", &SE3::inverse, "Compute the inverse.") + .def(nb::self * nb::self, "Compose two rigid body transformations.") + .def(nb::self == nb::self, "Check for equality") + .def(nb::self != nb::self, "Check for inequality") .def("__repr__", &SE3::toString) .def("__copy__", [](const SE3 &self) { return SE3(self); }) .def( @@ -235,7 +310,11 @@ inline void makeTypes(nb::module_ &m) { .qw = std::get<3>(t)}, Eigen::Vector3d{std::get<4>(t), std::get<5>(t), std::get<6>(t)}); - }); + }) + .doc() = "SE3 class for representing a 3D rigid body transformation " + "using a quaternion and a translation vector. This is outfitted " + "with some basic functionality, but mostly intended for storage " + "and converting between types."; } } // namespace evalio \ No newline at end of file diff --git a/docs/css/tweaks.css b/docs/css/tweaks.css new file mode 100644 index 00000000..cc5d58a8 --- /dev/null +++ b/docs/css/tweaks.css @@ -0,0 +1,13 @@ +@media screen and (min-width: 76.25em) { + + /* remove top level header */ + /* https://github.com/squidfunk/mkdocs-material/discussions/8192#discussioncomment-13013188 */ + label[for="__drawer"] { + display: none; + } + + /* recolor top level headers */ + .md-nav__item--section>.md-nav__link[for] { + color: var(--md-default-fg-color--lighter) !important; + } +} \ No newline at end of file diff --git a/docs/examples/data_loading.md b/docs/examples/data_loading.md new file mode 100644 index 00000000..22a37088 --- /dev/null +++ b/docs/examples/data_loading.md @@ -0,0 +1,92 @@ +Loading robotics datasets can be tedious and time-consuming due to the lack of standardization in the datasets formats. evalio provides a unified interface for loading datasets, making it trivial to get up and running with real-world lidar and IMU data. + +Datasets are managed via the CLI interface, specifically, using `evalio dl` to download datasets and `evalio rm` command to remove unwanted datasets. `evalio ls datasets` is also useful to visualize what has been installed. + +Once a dataset is downloaded, it can be used via the `evalio` library. Each dataset is an enum, with each sequence being a member. + +```python +from evalio.datasets import Hilti2022 + +data = Hilti2022.basement_2 +``` + +Each datasets provides helpers for iterating over data, + +```python +from evalio.datasets import Hilti2022 + +# Iterate through all data in the dataset +for mm in Hilti2022.basement_2: + print(mm) + +# Access only lidar scans +for scan in Hilti2022.basement_2.lidar(): + print(scan) + +# Access only IMU data +for imu in Hilti2022.basement_2.imu(): + print(imu) +``` + +Each time an iteration function is called, it will reload the data from the beginning. This obviously has a performance cost, so use wisely. + +Helpers also exist for just gathering a single data point, + +```python +from evalio.datasets import Hilti2022 + +# Get the 10th IMU data point +imu = Hilti2022.basement_2.get_one_imu(10) + +# Get the 10th lidar data point +lidar = Hilti2022.basement_2.get_one_lidar(10) +``` + +!!! warning + + There is a cost to these single data point functions; at the moment this will cause a full iteration over the dataset to find the data point. This is a known issue and will be fixed in the future. + +Each lidar measurement (type [LidarMeasurement][evalio.types.LidarMeasurement]) consists of a stamp and a vector of points (type [Point][evalio.types.Point]). The measurement follows a meticulous order, and always adhered to the following properties, + +- The measurement stamp is always at the start of the scan +- Scans are in row-major order +- Scans contain all points, i.e. invalid points are NOT dropped. This is important information that is used in some algorithms for feature extractions. For example, a 128x1024 scan will contain 128x1024 points +- Points stamps are always relative to the start of the scan, so absolute point times can be calculated via `scan.stamp + scan.points[i].stamp` + +Points are then easy to work with, as can be seen below. +```python +from evalio.datasets import Hilti2022 +import matplotlib.pyplot as plt +import numpy as np + +scan = Hilti2022.basement_2.get_one_lidar(10) + +# Extract x, y, z coordinates +x = np.array([p.x for p in scan.points]) +y = np.array([p.y for p in scan.points]) +z = np.array([p.z for p in scan.points]) + +# Plot the bird's-eye view +plt.scatter(x, y, c=z, s=1) +plt.axis('equal') +plt.title("Bird's-Eye View of Point Cloud") +plt.show() +``` + +Additionally, a conversion to rerun types is also available for easy visualization, see [rerun][evalio.rerun] for more details. + +```python +import rerun as rr +from evalio.rerun import convert + +# Initialize rerun +rr.init("evalio") +rr.connect_tcp() + +# Stream lidar scans to rerun +for scan in Hilti2022.basement_2.lidar(): + rr.set_time_seconds("timeline", seconds=scan.stamp.to_sec()) + rr.log("lidar", convert(scan, color="z")) +``` + +If anything is unclear, please open an issue on the evalio repository. We are always looking to improve the documentation and make it easier to use. \ No newline at end of file diff --git a/docs/examples/dataset.md b/docs/examples/dataset.md new file mode 100644 index 00000000..7bd4d872 --- /dev/null +++ b/docs/examples/dataset.md @@ -0,0 +1,85 @@ +While evalio comes with a number of built-in datasets, you can also easily create your own dataset without having to build any of evalio from source. In addition to this guide, we also provide [evalio-example](https://github.com/contagon/evalio-example) with examples. + +To get evalio to find your custom dataset, simply point the environment variable `EVALIO_CUSTOM=my_module` to the module where your dataset is defined. + +To create a dataset, one simply has to inherit from the `Dataset` class, + +```python +from evalio.datasets import Dataset, DatasetIterator +from evalio.types import Trajectory, SE3, ImuParams, LidarParams + +from typing import Sequence, Optional +from pathlib import Path +from enum import auto + +class MyDataset(Dataset): + sequence_1 = auto() + sequence_2 = auto() + sequence_3 = auto() + + def data_iter(self) -> DatasetIterator: ... + + def ground_truth_raw(self) -> Trajectory: ... + + def files(self) -> Sequence[str | Path]: ... + + def imu_T_lidar(self) -> SE3: ... + + def imu_T_gt(self) -> SE3: ... + + def imu_params(self) -> ImuParams: ... + + def lidar_params(self) -> LidarParams: ... +``` +The most obvious thing to note is the list of sequences; as each Dataset class is an enum, we list all of the sequences as enum members. + +After that are the methods. The last four methods are fairly self-explanatory, but we'll elaborate more on the first three. + +## data_iter + +Arguably the most important method, as is the main interface to the actual data. It returns a [`DatasetIterator`][evalio.datasets.DatasetIterator] object, which provides iterators over the data. + +While you are welcome to provide a custom [`DatasetIterator`][evalio.datasets.DatasetIterator], evalio provides some for the most common use cases in [`RosbagIter`][evalio.datasets.RosbagIter] that iterates over topics found in a rosbag, and [`RawDataIter`][evalio.datasets.RawDataIter] takes in iterators for imu and lidar data to return. + +An example of [`RosbagIter`][evalio.datasets.RosbagIter] can be found in the [Hilti2022 source code](https://github.com/contagon/evalio/blob/master/python/evalio/datasets/hilti_2022.py) and [`RawDataIter`][evalio.datasets.RawDataIter] in the [Helipr source code](https://github.com/contagon/evalio/blob/master/python/evalio/datasets/helipr.py). There is also examples of each in [evalio-example](https://github.com/contagon/evalio-example). + +## ground_truth_raw + +This method provides the ground truth trajectory in an arbitrary frame (often a GPS or prism frame). Many times this is provided in a csv file, which can be loaded via [`Trajectory.from_tum`][evalio.types.Trajectory.from_tum] or[`Trajectory.from_csv`][evalio.types.Trajectory.from_csv]. Naturally, [`Trajectory`][evalio.types.Trajectory] can also be created manually from a list of [`Stamp`][evalio.types.Stamp] and [`SE3`][evalio.types.SE3] objects. + +It will be transformed into the IMU frame using transform given by the `imu_T_gt` method. + +## files + +This provides a list of files that are required to run the dataset. evalio will internally use them to check to make sure the dataset is complete before running anything. + +If a returned file is a `str` it is assumed to be a path relative to the sequence directory in `EVALIO_DATA`, which is given by [`folder`][evalio.datasets.Dataset.folder]. If it is a `Path` object, it is assumed to be an absolute path stored wherever you desire. + +## optionals + +Additionally, there is a number of optional methods that you can implement to add more functionality and information to the `evalio ls` command. Each of these already have default implementations. These methods are (continuing from the above example): + +```python + @classmethod + def dataset_name(cls) -> str: ... + + @staticmethod + def url() -> str: ... + + def environment(self) -> str: ... + + def vehicle(self) -> str: ... + + def download(self) -> str: ... + + def quick_len(self) -> Optional[int]: ... +``` +The first one provides a base dataset name to be used. By default this is the class name converted to snake case, but it can be overridden. This will be the name used when running CLI commands. + +The next three are again self-explanatory, all of which provide information for the `evalio ls` command. + +`download` does exactly what it says - downloads the datasets. See the [newer college 2020](https://github.com/contagon/evalio/blob/master/python/evalio/datasets/newer_college_2020.py#L157) for an example downloading from google drive, and [hilti 2022](https://github.com/contagon/evalio/blob/master/python/evalio/datasets/hilti_2022.py#L144) for an example downloading directly from a url. + +`quick_len` returns a hardcoded number of scans in a dataset, used for `evalio ls` and for computing time estimates in `evalio run`. If not set, evalio will load the data to compute the length. + +That's all there is to it! Datasets are fairly simple - mostly just parameter setting and easy-to-use iterator wrappers. If you have an dataset implementation of an open-source dataset, feel free to make a PR to add it to evalio so others can use it as well. \ No newline at end of file diff --git a/docs/examples/evaluation.md b/docs/examples/evaluation.md new file mode 100644 index 00000000..476ca7fb --- /dev/null +++ b/docs/examples/evaluation.md @@ -0,0 +1,65 @@ +One of the main uses of `evalio` is to evaluate the performance of lidar-inertial odometry pipelines. The `evalio run` command is dedicated to this task. It has usage both as a quick command, and via loading of a config file. See [cli](../ref/cli.md) for more details. + +!!! tip + + evalio also comes with autocomplete, which makes typing the long dataset and pipeline names much easier. To install, do one of the following, + ```bash + eval "$(evalio --show-completion)" # install for the current session + evalio --install-completion # install for all future sessions + ``` + +## Quick command +For one off evaluations without parameter changes, `evalio run` can be used directly, +```bash +evalio run -d newer_college_2020/short_experiment \ + -d hilti_2022/basement_2 \ + -p kiss -p liosam \ + -o evalio_results +``` +Also available are the `-l/--length` which will set a maximum length for every dataset run on, and `-s/--show` which will visualize the results in an open rerun window, with `m` showing the map, `s` the scan, `f` the extracted features, and `i` the intensity image. + + +## Config file +For more complex evaluations, a config file can be used. Here's an example config file, +```yaml +output_dir: ./results/ + +datasets: + # Run on all of newer college trajectories + - hilti_2022/* + # Run on first 1000 scans of multi campus + - name: multi_campus/ntu_day_01 + length: 1000 + +pipelines: + # Run vanilla kiss with default parameters + - kiss + # Tweak kiss parameters + - name: kiss_tweaked + pipeline: kiss + deskew: true + # Some of these datasets need smaller voxel sizes + sweep: + voxel_size: [0.1, 0.5, 1.0] +``` + +A few notes, +- For datasets, a wildcard `*` can be used to run on all sequences in that dataset. +- In the dataset section, a bare item `- hilti_2022/basement_2` is shorthand for `- name: hilti_2022/basement_2`. +- Similarly for pipelines, a bare item `- kiss` is shorthand for `- pipeline: kiss`. +- If a pipeline name is not set, it defaults to the pipeline name. +- The `sweep` section is used to run the pipeline with different parameters. The parameters are set as a list, and the pipeline will be run for each parameter in the list, with the name of the pipeline being set to `name__parameter_value`. + +The config file can be run with, +```bash +evalio run -c config.yaml +``` +Results will be saved to the `output_dir` specified in the config file, with nested results based on the dataset. Visualization options can also be used here. Mixing the config file and command line options is not allowed, just for reducing the number of possible combinations. + +## Evaluating + +Once trajectories have been run, statistics can be calculated, +```bash +evalio stats -d results -m mean -w 200 -s RTEt +``` +With `-m/--metric` specifying the metric to calculate with options including mean, median, and sse and `-w/--window` specifying the window size for RTE, with a default of 100 scans. Only first part of all trajectories can also be done using the `-l/--length` option. Sorting of the results can be done with the `-s/--sort` option, with any column heading being an allowed option. \ No newline at end of file diff --git a/docs/examples/odometry.md b/docs/examples/odometry.md new file mode 100644 index 00000000..c0fba751 --- /dev/null +++ b/docs/examples/odometry.md @@ -0,0 +1,55 @@ +If one off odometry is needed, such as for testing a loop closure algorithm, evalio can generate it for you. For example, to generate odometry for the first 2000 scans of the Newer College 2020 dataset using KissICP, +```bash +evalio run -p kiss -d newer_college_2020/short_experiment -o odometry.csv -l 2000 +``` + +The odometry will be *in the IMU frame* and saved in the TUM format, +$$ +[t\ \text{(sec)}, p_x, p_y, p_z, q_x, q_y, q_z, q_w\] +$$ +along with metadata about how the trajectory was generated behind the '#' character. The above for the first 10 scans would look like: +```csv +# name: kiss +# pipeline: kiss +# version: 1.2.2 +# convergence_criterion: 0.0001 +# deskew: False +# initial_threshold: 2.0 +# max_num_iterations: 500 +# max_num_threads: 0 +# max_points_per_voxel: 20 +# min_motion_th: 0.1 +# voxel_size: 1.0 +# +# dataset: newer_college_2020 +# sequence: short_experiment +# length: 100 +# +# timestamp, x, y, z, qx, qy, qz, qw +1583836591.082580992,0.006252999883145094,-0.011775000020861626,-0.007644999772310257,0.0,0.0,-1.0,0.0 +1583836591.182590976,0.002104584749512301,-0.014210604585971584,-0.007355713813266136,-2.675152057596367e-05,-8.363399211902303e-06,-0.9999999975888385,-6.353528931178803e-05 +1583836591.282592512,0.0027150532454897226,-0.004297982387396928,-0.015746353734418864,-4.334993947940944e-05,-6.124874074873121e-05,-0.999999996655854,3.2521785965193945e-05 +1583836591.382579456,0.00597131720748457,-0.011520632994006833,-0.006492858132001103,6.470950992969571e-05,-4.590305780285223e-05,-0.9999999968522549,1.0386708003358978e-06 +1583836591.482568704,0.007478720199430491,-0.009223450849391,-0.003496780595402071,1.4720267758293692e-06,3.463391730777789e-05,-0.9999999993679838,7.896668660298116e-06 +1583836591.582553088,0.0012911112076719805,-0.012163729075573013,-0.001935118832270371,1.711692196181323e-05,5.2587891622865136e-05,-0.9999999978422404,3.545481795975868e-05 +1583836591.682529024,0.0026154127249510307,-0.0111173632560525,-0.005718297889001694,-2.7387527703120896e-05,2.907679171021379e-05,-0.9999999988611495,-2.6118280969886083e-05 +1583836591.782496512,0.0042376370480087765,-0.01660435415391814,-0.004709413029178095,-5.202183158898017e-05,6.121947284618483e-05,-0.9999999961629367,-3.492895473336578e-05 +1583836591.882515968,0.003165756615792398,-0.012502098488453172,-0.0014189397620055577,-1.48525422597923e-05,6.736687040505698e-05,-0.9999999976119399,4.150543609892368e-06 +1583836591.982548736,-0.000298752072032382,-0.009307858981044354,-0.01089797673030992,-1.0989202198288011e-05,-4.521269654356091e-05,-0.999999998085071,4.0803279324257364e-05 +``` + +This file can then by loaded and aligned to the ground truth as needed using the python API. +```python +from evalio.types import Trajectory +from evalio.datasets import NewerCollege2020 +from evalio import stats + +traj = Trajectory.from_experiment("odometry.csv") +gt = NewerCollege2020.short_experiment.ground_truth() + +# Align the odometry to the ground truth +traj_aligned, gt_aligned = stats.align(traj, gt) + +# Compute metrics as desired (will align if not already aligned) +error = stats.rte(traj, gt).mean() +``` \ No newline at end of file diff --git a/docs/examples/pipeline.md b/docs/examples/pipeline.md new file mode 100644 index 00000000..a06b187b --- /dev/null +++ b/docs/examples/pipeline.md @@ -0,0 +1,148 @@ +evalio comes with a small number of built-in pipelines, but is made to be easily extensible. Custom pipelines can be created in C++ with nanobind, or in Python. See [evalio-example](https://github.com/contagon/evalio-example) for some examples of building C++ pipelines as well as custom python pipelines. + +To get evalio to find your custom pipeline, simply point the environment variable `EVALIO_CUSTOM=my_module` to the module where your pipeline is defined. + +To create a pipeline, simply inherit from the `Pipeline` class, + +=== "Python" + + ```python + from evalio.pipelines import Pipeline + from evalio.types import ( + SE3, + Point, + ImuParams, + LidarParams, + ImuMeasurement, + LidarMeasurement, + ) + + class MyPipeline(Pipeline): + def __init__(self): + super().__init__() + + # Info + @staticmethod + def version() -> str: ... + @staticmethod + def url() -> str: ... + @staticmethod + def name() -> str: ... + @staticmethod + def default_params() -> dict[str, bool | int | float | str]: ...; + + # Getters + def pose(self) -> SE3: ... + def map(self) -> list[Point]: ... + + # Setters + def set_imu_params(self, params: ImuParams): ... + def set_lidar_params(self, params: LidarParams): ... + def set_imu_T_lidar(self, T: SE3): ... + def set_params(self, params: dict[str, bool | int | float | str]): ... + + # Doers + def initialize(self): ... + def add_imu(self, mm: ImuMeasurement): ... + def add_lidar(self, mm: LidarMeasurement) -> list[Point]: ... + ``` + +=== "C++" + + ``` c++ + #include "evalio/pipeline.h" + #include "evalio/types.h" + + #include + #include + #include + #include + + namespace nb = nanobind; + + class MyPipeline : public evalio::Pipeline { + public: + MyPipeline() : evalio::Pipeline() {} + + // Info + static std::string version() { ... } + static std::string url() { ... } + static std::string name() { ... } + static std::map default_params() { ... }; + + // Getters + const SE3 pose() { ... }; + const std::vector map() { ... }; + + // Setters + void set_imu_params(ImuParams params) { ... }; + void set_lidar_params(LidarParams params) { ... }; + void set_imu_T_lidar(SE3 T) { ... }; + void set_params(std::map) { ... }; + + // Doers + void initialize() { ... }; + void add_imu(ImuMeasurement mm) { ... }; + std::vector add_lidar(LidarMeasurement mm) { ... }; + } + ``` + +We'll cover each section of methods in turn. + +## Info + +The first four methods are all static methods that provide information about the pipeline. `version`, `url`, and `name` are all self-explanatory. `default_params` is a static method that returns a dictionary of the default parameters for the pipeline. This is used to verify parameters before they are passed in, as well as ensure a consistent output for each run. + +## Getters + +The next two methods are getters for the pose and map. The pose is the most up-to-date estimate for the IMU and is polled after each lidar measurement is passed in. + +The map current map/submap/etc and is only used for visualization purposes. + +## Setters + +These are to set both dataset specific parameters and pipeline specific parameters. The dataset specific parameters are `imu_params`, `lidar_params`, and `imu_T_lidar`. These are all set before the pipeline is run. + +The pipeline specific parameters are set using `set_params`, which takes in a dictionary of parameters. This is used to set any parameters that are specific to the pipeline, such as the number of iterations or the convergence threshold. `default_params` is updated with any parameters and passed at the start of each run. + +## Doers +Arguably the most important part. + +`initialize` is called right after all parameters are set. Think of it as a delayed constructor. + +`add_imu` is called for each IMU measurement. This is where the IMU data is processed and used to update the pose. + +`add_lidar` is called for each lidar measurement. This is where the lidar data is processed and used to update the map. It returns a list of features were extracted from the scan and are used for visualization. + +## C++ Building + +If done in C++, you will need to build the pipeline as a shared library. This is done by a nanobind wrapper, which can be defined at the bottom of your file as follows, +```c++ +NB_MODULE(_core, m) { + m.doc() = "Custom evalio pipeline example"; + + nb::module_ evalio = nb::module_::import_("evalio"); + + // Only have to override the static methods here + // All the others will be automatically inherited from the base class + nb::class_(m, "MyCppPipeline") + .def(nb::init<>()) + .def_static("name", &MyCppPipeline::name) + .def_static("url", &MyCppPipeline::url) + .def_static("default_params", &MyCppPipeline::default_params); +} +``` + +We recommend then setting everything up to be built with [`scikit-build-core`](https://scikit-build-core.readthedocs.io/en/latest/). You can see [evalio-example](https://github.com/contagon/evalio-example) for an example of how to set this up. + +!!! warning + + In order for nanobind to share types between the `evalio` shared object and your custom pipeline, they will have to be compiled with the same version of `libstdc++`. This [pybind PR](https://github.com/pybind/pybind11/pull/5439) discusses this in more detail. + + The "abi_tag" used in your version of evalio can be gotten using `evalio._abi_tag()`, or by running `python -c "import evalio; print(evalio._abi_tag())`". To make sure it matches your nanobind module's, add this to your `NB_MODULE` definition: + + ```c++ + m.def("abi_tag", []() { return nb::detail::abi_tag(); }); + ``` + +That's all there is to it! Pipelines should be fairly easy to implement and are usually just a simple wrapper around your existing code to provide a common interface. Once your pipeline is open-source/published/etc, feel free to make a PR to add it to evalio. This both improves the visibility of your work and of evalio. \ No newline at end of file diff --git a/docs/included.py b/docs/included.py new file mode 100644 index 00000000..1af40217 --- /dev/null +++ b/docs/included.py @@ -0,0 +1,95 @@ +import mkdocs_gen_files +from evalio.cli.ls import ls, Kind +from rich.table import Table +from typing import Optional, cast + + +def clean_cell(cell: str) -> str: + """Clean a cell by removing unwanted characters.""" + # Remove rich text formatting + cell = cell.replace("[bright_black]", "").replace("[/bright_black]", "") + # Remove line breaks + cell = cell.replace("\n", "
") + # Non line breaking space + cell = cell.replace(" ", " ") + # Non line breaking hyphen + cell = cell.replace("-", "‑") + # Convert to links + if cell.startswith("http"): + cell = f"[link]({cell})" + + return cell.strip() + + +def center_code(kind: str) -> str: + """Convert a justification code to a markdown table alignment code.""" + match kind: + case "left": + return ":--" + case "center": + return ":--:" + case "right": + return "--:" + case _: + return "---" + + +def rich_table_to_markdown( + table: Table, skip_columns: Optional[list[str]] = None +) -> str: + """Convert a rich Table to a markdown table string.""" + if skip_columns is None: + skip_columns = [] + + # Remove unwanted columns + good_columns = [c for c in table.columns if c.header not in skip_columns] + + # Gather all of the data + headers = [cast(str, column.header) for column in good_columns] + bars = [center_code(column.justify) for column in good_columns] + columns = [ + [clean_cell(cast(str, c)) for c in column._cells] for column in good_columns + ] + rows = zip(*columns) + + # Create markdown table header + markdown = "| " + " | ".join(headers) + " |\n" + markdown += "| " + " | ".join(bars) + " |\n" + + # Add rows to markdown table + for row in rows: + markdown += "| " + " | ".join(row) + " |\n" + + return markdown + + +DATASETS = """--- +hide: + - toc +--- +evalio comes with a variety of datasets that can be used for easy loading. Below is a table of all datasets that are included, which mirrors the output of `evalio ls datasets`. +""" + +with mkdocs_gen_files.open("included/datasets.md", "w") as f: + f.write(DATASETS) + f.write("\n") + + table = ls(Kind.datasets, show=False, show_hyperlinks=True) + if table is not None: + f.write(rich_table_to_markdown(table, skip_columns=["DL", "Size"])) + + +PIPELINES = """--- +hide: + - toc +--- +evalio comes with a variety of pipelines that can be used for evaluation. Below is a table of all pipelines that are included and their parameters, which mirrors the output of `evalio ls pipelines`. +""" + +with mkdocs_gen_files.open("included/pipelines.md", "w") as f: + f.write(PIPELINES) + f.write("\n") + + table = ls(Kind.pipelines, show=False, show_hyperlinks=True) + if table is not None: + f.write(rich_table_to_markdown(table)) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..a3a9f8cc --- /dev/null +++ b/docs/index.md @@ -0,0 +1,15 @@ +# evalio + +evalio is a tool for **Eval**uating **L**idar-**I**nertial **O**dometry. + +Specifically, it provides a common interface for using LIO datasets and LIO pipelines. This allows for easy addition of new datasets and pipelines as well as a common location to evaluate them. This makes benchmarks significantly easier to run and data significantly easier to access. It features, + +- Download and manage datasets via the CLI interface +- Simple to use python package API for friction-free access to data +- No ROS dependency! (though it can still load rosbag datasets using the wonderful [rosbags](https://ternaris.gitlab.io/rosbags/) package) +- Easy to add new datasets and pipelines, see the [example](https://github.com/contagon/evalio-example) +- Unified representation of lidar scan, e.g. row (scan-line) major order, stamped at the start of the scan, point stamps are relative from the start of the scan. +- Run pipelines via the CLI interface and yaml config files +- Compute statistics for resulting trajectory runs + +Checkout [quickstart](quickstart.md) for a quick overview of the package. Additionally, evalio eases a number of common tasks, check out the examples for some common use cases. \ No newline at end of file diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 00000000..567db137 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,31 @@ +For the majority of use cases, we simply recommend installing via your favorite python package manager, +```bash +uv add evalio # uv +pip install evalio # pip +``` + +## Building from Source +Building all the available pipelines together is difficult, so we've done our best to separate a build option for just the core types of evalio, and then pipelines can be optionally added in. + +### Base +We've attempted to make building from source as easy as possible. We generally build through [scikit-core-build](https://scikit-build-core.readthedocs.io/) which provides a simple wrapper for building CMake projects as python packages. `uv` is our frontend of choice for this process, but it is also possible via pip +```bash +uv sync --verbose # uv version +pip install -e . # pip version +``` + +Of course, building via the usual `CMake` way is also possible, with the only default dependency being `Eigen3`, +```bash +mkdir build +cd build +cmake .. +make +``` + +### Pipelines +By default, pipelines are not included due to their large dependencies. We use vpckg to handle a reliable build of these dependencies and pipelines. vcpkg and the pipelines can be setup via running +```bash +./cpp/setup_pipelines.sh +``` + +This will clone and setup vcpkg in the `.vcpkg` directory, and the pipelines (including some minor patches) to `cpp/bindings/pipelines-src`. vcpkg will automatically be picked up by CMake in this directory, so the build process then continues as in the base section. diff --git a/docs/javascripts/katex.js b/docs/javascripts/katex.js new file mode 100644 index 00000000..a9417bf6 --- /dev/null +++ b/docs/javascripts/katex.js @@ -0,0 +1,10 @@ +document$.subscribe(({ body }) => { + renderMathInElement(body, { + delimiters: [ + { left: "$$", right: "$$", display: true }, + { left: "$", right: "$", display: false }, + { left: "\\(", right: "\\)", display: false }, + { left: "\\[", right: "\\]", display: true } + ], + }) +}) \ No newline at end of file diff --git a/docs/javascripts/tablesort.js b/docs/javascripts/tablesort.js new file mode 100644 index 00000000..ee04e900 --- /dev/null +++ b/docs/javascripts/tablesort.js @@ -0,0 +1,6 @@ +document$.subscribe(function () { + var tables = document.querySelectorAll("article table:not([class])") + tables.forEach(function (table) { + new Tablesort(table) + }) +}) \ No newline at end of file diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 00000000..c1fd7d8d --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,142 @@ + +evalio is available on PyPi, so simply install via your favorite python package manager, +```bash +uv add evalio # uv +pip install evalio # pip +``` + +evalio can be used both as a python library and as a [CLI](../ref/cli) for both datasets and pipelines. + +## Datasets + +Once evalio is installed, datasets can be listed and downloaded via the [CLI](../ref/cli) interface. For example, to list all datasets and then download a sequence from the hilti-2022 dataset, +```bash +evalio ls datasets +evalio download hilti_2022/basement_2 +``` +evalio downloads data to the path given by `-D`, `EVALIO_DATA` environment variable, or if both are unset to the local folder `./evalio_data`. All the trajectories in a dataset can also be downloaded by using the wildcard `hilti_2022/*`, making sure to escape the asterisk as needed. + +!!! tip + + evalio also comes with autocomplete, which makes typing the long dataset and pipeline names much easier. To install, do one of the following, + ```bash + eval "$(evalio --show-completion)" # install for the current session + evalio --install-completion # install for all future sessions + ``` + +!!! note + + Many datasets use [gdown](https://github.com/wkentaro/gdown) to download datasets from google drive. Unfortunately, this can occasionally be finicky due to google's download limits, however [downloading cookies from your browser](https://github.com/wkentaro/gdown?tab=readme-ov-file#i-set-the-permission-anyone-with-link-but-i-still-cant-download) can often help. + + +Once downloaded, a trajectory can then be easily used in python, +```python +from evalio.datasets import Hilti2022 + +# for all data +for mm in Hilti2022.basement_2: + print(mm) + +# for lidars +for scan in Hilti2022.basement_2.lidar(): + print(scan) + +# for imu +for imu in Hilti2022.basement_2.imu(): + print(imu) +``` + +For example, you can easily get a single scan to plot a bird-eye view, +```python +import matplotlib.pyplot as plt +import numpy as np + +# get the 10th scan +scan = Hilti2022.basement_2.get_one_lidar(10) +# always in row-major order, with stamp at start of scan +x = np.array([p.x for p in scan.points]) +y = np.array([p.y for p in scan.points]) +z = np.array([p.z for p in scan.points]) +plt.scatter(x, y, c=z, s=1) +plt.axis('equal') +plt.show() +``` +evalio also comes with a built wrapper for converting to [rerun](https://rerun.io) types, +```python +import rerun as rr +from evalio.rerun import convert + +rr.init("evalio") +rr.connect_tcp() +for scan in Hilti2022.basement_2.lidar(): + rr.set_time_seconds("timeline", seconds=scan.stamp.to_sec()) + rr.log("lidar", convert(scan, color=[255, 0, 255])) +``` + +!!! note + + To run the rerun visualization, rerun must be installed. This can be done by installing `rerun-sdk` or `evalio[vis]` from PyPi. + + Once installed, rerun must be spawned from the CLI simply by running `rerun` in a terminal. This will start the rerun viewer, which can then be used to visualize the data logged by evalio. + +We recommend checking out the [API reference][evalio.datasets.Dataset] for more information on how to interact with datasets, and the [example](examples/dataset.md) for an example of how to create your own dataset. + +## Pipelines + +The other half of evalio is the pipelines that can be run on various datasets. All pipelines and their parameters can be shown via, +```bash +evalio ls pipelines +``` +For example, to run KissICP on a dataset, +```bash +evalio run -o results -d hilti_2022/basement_2 -p kiss +``` +This will run the pipeline on the dataset and save the results to the `results` folder. The results can then be used to compute statistics on the trajectory, +```bash +evalio stats results +``` +!!! note + + KissICP does poorly by default on hilti_2022/basement_2, due to the close range and large default voxel size. You can visualize this by adding `-s ms` to the `run` command to visualize the map and scan in rerun. + +More complex experiments can be run, including varying pipeline parameters, via specifying a config file, +```yaml +output_dir: ./results/ + +datasets: + # Run on all of newer college trajectories + - hilti_2022/* + # Run on first 1000 scans of multi campus + - name: multi_campus/ntu_day_01 + length: 1000 + +pipelines: + # Run vanilla kiss with default parameters + - kiss + # Tweak kiss parameters + - name: kiss_tweaked + pipeline: kiss + deskew: true + # Some of these datasets need smaller voxel sizes + sweep: + voxel_size: [0.1, 0.5, 1.0] + +``` +This can then be run via +```bash +evalio run -c config.yml +``` + +Additionally, the run command supports visualization via rerun as well. `-v` will do a simple visualization of the ground truth trajectory and odometry, while `-s` can be used to enable additional visualizations, +```bash +evalio run -o results -d hilti_2022/basement_2 -p kiss -s msif +``` +where m -> map, s -> scan, i -> intensity image, and f -> extracted features can all be selectively visualized in rerun. + +!!! note + + As mentioned above, rerun must be installed and launched to visualize the results. + +That's about the gist of it! Try playing around the [CLI](../ref/cli) interface to see what else is possible. Feel free to open an issue if you have any questions, suggestions, or problems. + +Additionally, we recommend checking out the examples section for specific use cases for evalio. \ No newline at end of file diff --git a/docs/ref/cli.py b/docs/ref/cli.py new file mode 100644 index 00000000..511ba160 --- /dev/null +++ b/docs/ref/cli.py @@ -0,0 +1,7 @@ +from typer.cli import app +from contextlib import redirect_stdout +import mkdocs_gen_files + +with mkdocs_gen_files.open("ref/cli.md", "w") as f: + with redirect_stdout(f): + app(["evalio.cli", "utils", "docs", "--name", "evalio"]) diff --git a/docs/ref/datasets.md b/docs/ref/datasets.md new file mode 100644 index 00000000..28506a51 --- /dev/null +++ b/docs/ref/datasets.md @@ -0,0 +1,24 @@ +For more information about the datasets included in evalio, see the [included datasets](../included/datasets.md) section. + +::: evalio.datasets + options: + show_submodules: true + members: + - Dataset + - DatasetIterator + - RosbagIter + - RawDataIter + - get_data_dir + - set_data_dir + +::: evalio.datasets + options: + show_root_toc_entry: false + show_labels: false + filters: + - "!Dataset" + - "!DatasetIterator" + - "!RosbagIter" + - "!RawDataIter" + - "!get_data_dir" + - "!set_data_dir" \ No newline at end of file diff --git a/docs/ref/pipelines.md b/docs/ref/pipelines.md new file mode 100644 index 00000000..71b7d482 --- /dev/null +++ b/docs/ref/pipelines.md @@ -0,0 +1,12 @@ +For more information about the pipelines included in evalio, see the [included pipelines](../included/pipelines.md) section. + +::: evalio.pipelines + options: + members: + - Pipeline + +::: evalio.pipelines + options: + show_root_toc_entry: false + filters: + - "!Pipeline" \ No newline at end of file diff --git a/docs/ref/rerun.md b/docs/ref/rerun.md new file mode 100644 index 00000000..3ff7a5d5 --- /dev/null +++ b/docs/ref/rerun.md @@ -0,0 +1 @@ +::: evalio.rerun \ No newline at end of file diff --git a/docs/ref/stats.md b/docs/ref/stats.md new file mode 100644 index 00000000..d83749d4 --- /dev/null +++ b/docs/ref/stats.md @@ -0,0 +1 @@ +::: evalio.stats \ No newline at end of file diff --git a/docs/ref/types.md b/docs/ref/types.md new file mode 100644 index 00000000..17d12eda --- /dev/null +++ b/docs/ref/types.md @@ -0,0 +1 @@ +::: evalio.types \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..4f5bc656 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,131 @@ +site_name: evalio +site_url: http://contagon.github.io/evalio +repo_url: https://github.com/contagon/evalio +repo_name: contagon/evalio + +extra: + version: + provider: mike + +theme: + name: material + features: + - navigation.instant # Instant loading of pages + - navigation.sections # split nav bar into sections + - navigation.top # button to go to top of the page + # - navigation.indexes + # - navigation.tabs + - content.code.copy # add code copy button + - content.tabs.link # make tabs sync + icon: + logo: material/circle-double + favicon: material/circle-double # TODO: Not sure if this works + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + - media: '(prefers-color-scheme: light)' + scheme: default + primary: black + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + primary: black + toggle: + icon: material/brightness-4 + name: Switch to system preference + +watch: + - python/ + - cpp/ + +nav: + - evalio: + - About: index.md + - Quickstart: quickstart.md + - Source Build: install.md + - Examples: + - Odometry: examples/odometry.md + - Data Loading: examples/data_loading.md + - Evaluation: examples/evaluation.md + - Custom Dataset: examples/dataset.md + - Custom Pipeline: examples/pipeline.md + - Included: + - Datasets: included/datasets.md + - Pipelines: included/pipelines.md + - Reference: + - cli: ref/cli.md + - evalio.datasets: ref/datasets.md + - evalio.pipelines: ref/pipelines.md + - evalio.stats: ref/stats.md + - evalio.types: ref/types.md + - evalio.rerun: ref/rerun.md + +plugins: + # Add top level search + search: + lang: en + + # autogenerate evalio ls pages + gen-files: + scripts: + - docs/included.py + - docs/ref/cli.py + + # Autogenerate docs from docstrings + mkdocstrings: + default_handler: python + handlers: + python: + options: + # TODO: May want to remove this once all docs are added + # show_if_no_docstring: true # show everything + show_source: false # don't show source code + separate_signature: true # show the signature in a separate section + show_signature_annotations: true # include types + signature_crossrefs: true # show cross-references in the signature + unwrap_annotated: true # remove Annotated[] from the signature + show_symbol_type_toc: true # include printed symbol inline with meth/class/etc + show_symbol_type_heading: true # include printed symbol in toc with meth/class/etc + + docstring_section_style: list # show parameters/args in a list + # show_labels: false + # show_category_heading: true + docstring_style: google + +markdown_extensions: + # misc + - tables + - admonition + + # math + - pymdownx.arithmatex: + generic: true + + # syntax highlighting + # https://squidfunk.github.io/mkdocs-material/reference/code-blocks/ + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite: + - pymdownx.superfences: + - pymdownx.details: + - pymdownx.tabbed: + alternate_style: true + +extra_javascript: + # auto sorting tables + # https://squidfunk.github.io/mkdocs-material/reference/data-tables/#sortable-tables-docsjavascriptstablesortjs + - javascripts/tablesort.js + - https://unpkg.com/tablesort@5.3.0/dist/tablesort.min.js + - javascripts/katex.js + - https://unpkg.com/katex@0/dist/katex.min.js + - https://unpkg.com/katex@0/dist/contrib/auto-render.min.js + +extra_css: + - https://unpkg.com/katex@0/dist/katex.min.css + - css/tweaks.css \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index c0e369dd..4c5d0fff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "rapidfuzz>=3.12.2", "rosbags>=0.10", "tqdm>=4.66", - "typer>=0.15.2", + "typer>=0.15.3", ] keywords = [ "lidar", @@ -27,7 +27,7 @@ keywords = [ license = { file = "LICENSE.txt" } [project.optional-dependencies] -vis = ["rerun-sdk>=0.18.2"] +vis = ["rerun-sdk>=0.23"] [build-system] requires = ["scikit-build-core>=0.8", "nanobind>=2.7.0", "numpy"] @@ -66,7 +66,7 @@ MACOSX_DEPLOYMENT_TARGET = "11" # local development [tool.uv] dev-dependencies = [ - "cmake>=3.30.3", + "cmake<4.0.0", "compdb>=0.2.0", "ruff>=0.6.8", "types-pyyaml>=6.0.12.20240917", @@ -76,12 +76,22 @@ dev-dependencies = [ "scipy>=1.15.2", "scipy-stubs>=1.15.2.1", "bump-my-version>=1.1.1", + "mkdocs>=1.6.1", + "mkdocstrings[python]>=0.29.1", + "mkdocs-material>=9.6.11", + "mkdocs-gen-files>=0.5.0", "nanobind>=2.7.0", + "mike>=2.1.3", ] [tool.ruff] exclude = ["cpp/**/*"] +[tool.ruff.format] +# https://docs.astral.sh/ruff/configuration/ +docstring-code-format = true +docstring-code-line-length = "dynamic" + [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/python/evalio/__init__.py b/python/evalio/__init__.py index 7e091e43..6cbba1a9 100644 --- a/python/evalio/__init__.py +++ b/python/evalio/__init__.py @@ -1,4 +1,4 @@ -from . import _cpp, datasets, pipelines, types, utils +from . import _cpp, datasets, pipelines, types, utils, stats from ._cpp import abi_tag as _abi_tag import warnings @@ -26,6 +26,7 @@ def cleanup(): "datasets", "_cpp", "pipelines", + "stats", "types", "utils", "__version__", diff --git a/python/evalio/cli/__init__.py b/python/evalio/cli/__init__.py index 863d223f..fe68c24b 100644 --- a/python/evalio/cli/__init__.py +++ b/python/evalio/cli/__init__.py @@ -32,12 +32,12 @@ def version_callback(value: bool): raise typer.Exit() -def data_callback(value: Optional[str]): +def data_callback(value: Optional[Path]): """ Set the data directory. """ if value: - set_data_dir(Path(value)) + set_data_dir(value) @app.callback() @@ -46,7 +46,7 @@ def global_options( # Once this fix is released (hasn't been as of 0.15.2), we can change it to a Path # https://github.com/fastapi/typer/pull/1138 data_dir: Annotated[ - Optional[str], + Optional[Path], typer.Option( "-D", "--data-dir", diff --git a/python/evalio/cli/ls.py b/python/evalio/cli/ls.py index d8b9b5ed..35e78d32 100644 --- a/python/evalio/cli/ls.py +++ b/python/evalio/cli/ls.py @@ -1,3 +1,4 @@ +from evalio.datasets.base import Dataset from .parser import DatasetBuilder, PipelineBuilder from typing import Optional, TypeVar, Annotated import typer @@ -22,6 +23,22 @@ def unique(lst: list[T]): return list(dict.fromkeys(lst)) +def extract_len(d: Dataset) -> str: + """Get the length of a dataset in a human readable format + + Args: + d (Dataset): Dataset to get length of + + Returns: + str: Length of dataset + """ + length = d.quick_len() + if length is None: + return "[bright_black]-[/bright_black]" + else: + return f"{length / d.lidar_params().rate / 60:.1f}min".rjust(7) + + class Kind(StrEnum): datasets = auto() pipelines = auto() @@ -49,15 +66,20 @@ def ls( help="Output less verbose information", ), ] = False, - links: Annotated[ + show_hyperlinks: Annotated[ bool, typer.Option( - "--links", - "-l", - help="Output full links to datasets. For terminals that don't support hyperlinks (OSC 8).", + "--show-hyperlinks", + help="Output full links. For terminals that don't support hyperlinks (OSC 8).", ), ] = False, -): + show: Annotated[ + bool, + typer.Option( + hidden=True, + ), + ] = True, +) -> Optional[Table]: """ List dataset and pipeline information """ @@ -79,14 +101,13 @@ def ls( # 3. Add the column to the table # That should be about it, making the rest should be automatic - # TODO: Could also add environment and vehicle as well - # Gather all info all_info = { "Name": [], "Sequences": [], "DL": [], "Size": [], + "Len": [], "Env": [], "Vehicle": [], "IMU": [], @@ -96,7 +117,7 @@ def ls( for d in to_include: all_info["Name"].append(d.dataset_name()) links_str = d.url() - if not links: + if not show_hyperlinks: links_str = f"[link={links_str}]link[/link]" all_info["Info"].append(links_str) @@ -135,6 +156,10 @@ def ls( ] ) all_info["Size"].append(size) + # length + all_info["Len"].append( + "\n".join([extract_len(d(s)) for s in d.sequences()]) + ) # misc info all_info["Env"].append("\n".join(env)) all_info["Vehicle"].append("\n".join(vehicle)) @@ -157,8 +182,10 @@ def ls( table.add_column("Name", justify="center", **col_opts) # type: ignore if not quiet: table.add_column("Sequences", justify="right", **col_opts) # type: ignore - table.add_column("DL", justify="center", **col_opts) # type: ignore + table.add_column("DL", justify="right", **col_opts) # type: ignore table.add_column("Size", justify="center", **col_opts) # type: ignore + if not quiet: + table.add_column("Len", justify="center", **col_opts) # type: ignore table.add_column("Env", justify="center", **col_opts) # type: ignore table.add_column("Vehicle", justify="center", **col_opts) # type: ignore table.add_column("IMU", justify="center", **col_opts) # type: ignore @@ -169,7 +196,10 @@ def ls( row_info = [all_info[c.header][i] for c in table.columns] # type: ignore table.add_row(*row_info) - Console().print(table) + if show: + Console().print(table) + + return table if kind == Kind.pipelines: # Search for pipelines using rapidfuzz @@ -200,7 +230,7 @@ def ls( for p in to_include: all_info["Name"].append(p.name()) links_str = p.url() - if not links: + if not show_hyperlinks: links_str = f"[link={links_str}]link[/link]" all_info["Info"].append(links_str) all_info["Version"].append(p.version()) @@ -236,4 +266,7 @@ def ls( row_info = [all_info[c.header][i] for c in table.columns] # type: ignore table.add_row(*row_info) - Console().print(table) + if show: + Console().print(table) + + return table diff --git a/python/evalio/cli/run.py b/python/evalio/cli/run.py index 511d5c18..14ca501f 100644 --- a/python/evalio/cli/run.py +++ b/python/evalio/cli/run.py @@ -21,7 +21,7 @@ @app.command(no_args_is_help=True, name="run", help="Run pipelines on datasets") def run_from_cli( config: Annotated[ - Optional[str], + Optional[Path], typer.Option( "-c", "--config", @@ -33,7 +33,7 @@ def run_from_cli( in_datasets: DatasetOpt = None, in_pipelines: PipelineOpt = None, in_out: Annotated[ - Optional[str], + Optional[Path], typer.Option( "-o", "--output", @@ -84,11 +84,10 @@ def run_from_cli( vis = RerunVis(vis_args) if config is not None: - pipelines, datasets, out = parse_config(Path(config)) + pipelines, datasets, out = parse_config(config) if out is None: print_warning("Output directory not set. Defaulting to './evalio_results'") out = Path("./evalio_results") - run(pipelines, datasets, out, vis) else: if in_pipelines is None: @@ -111,9 +110,15 @@ def run_from_cli( print_warning("Output directory not set. Defaulting to './evalio_results'") out = Path("./evalio_results") else: - out = Path(in_out) + out = in_out - run(pipelines, datasets, out, vis) + if out.suffix == ".csv" and (len(pipelines) > 1 or len(datasets) > 1): + raise typer.BadParameter( + "Output must be a directory when running multiple experiments", + param_hint="run", + ) + + run(pipelines, datasets, out, vis) def plural(num: int, word: str) -> str: @@ -129,6 +134,15 @@ def run( print( f"Running {plural(len(pipelines), 'pipeline')} on {plural(len(datasets), 'dataset')} => {plural(len(pipelines) * len(datasets), 'experiment')}" ) + lengths = [d.length if d.length is not None else len(d.build()) for d in datasets] + dtime = sum(le / d.dataset.lidar_params().rate for le, d in zip(lengths, datasets)) # type: ignore + dtime *= len(pipelines) + if dtime > 3600: + print(f"Estimated time (if real-time): {dtime / 3600:.2f} hours") + elif dtime > 60: + print(f"Estimated time (if real-time): {dtime / 60:.2f} minutes") + else: + print(f"Estimated time (if real-time): {dtime:.2f} seconds") print(f"Output will be saved to {output}\n") save_config(pipelines, datasets, output) diff --git a/python/evalio/cli/stats.py b/python/evalio/cli/stats.py index 179cc8b9..dc81b5c7 100644 --- a/python/evalio/cli/stats.py +++ b/python/evalio/cli/stats.py @@ -1,209 +1,19 @@ -from copy import deepcopy -from enum import StrEnum, auto from pathlib import Path from typing import Annotated, Optional, Sequence -import numpy as np -from dataclasses import dataclass from rich.table import Table from rich.console import Console from rich import box -from evalio.types import Stamp, Trajectory, SE3 +from evalio.types import Trajectory +from evalio import stats import typer -from typing import cast - app = typer.Typer() -# ------------------------- Functions to align trajectories ------------------------- # -def _check_overstep(stamps: list[Stamp], s: Stamp, idx: int) -> bool: - return abs((stamps[idx - 1] - s).to_sec()) < abs((stamps[idx] - s).to_sec()) - - -def align_stamps(traj1: Trajectory, traj2: Trajectory): - """Select the closest poses in traj1 and traj2. - - Operates in place. - - Does this by finding the higher frame rate trajectory and subsampling it to the closest poses of the other one. - Additionally it checks the beginning of the trajectories to make sure they start at about the same stamp. - - Args: - traj1 (Trajectory): One trajectory - traj2 (Trajectory): Other trajectory - - Returns: - tuple[Trajectory, Trajectory]: Sub-sampled trajectories - """ - # Check if we need to skip poses in traj1 - first_pose_idx = 0 - while traj1.stamps[first_pose_idx] < traj2.stamps[0]: - first_pose_idx += 1 - if _check_overstep(traj1.stamps, traj2.stamps[0], first_pose_idx): - first_pose_idx -= 1 - traj1.stamps = traj1.stamps[first_pose_idx:] - traj1.poses = traj1.poses[first_pose_idx:] - - # Check if we need to skip poses in traj2 - first_pose_idx = 0 - while traj2.stamps[first_pose_idx] < traj1.stamps[0]: - first_pose_idx += 1 - if _check_overstep(traj2.stamps, traj1.stamps[0], first_pose_idx): - first_pose_idx -= 1 - traj2.stamps = traj2.stamps[first_pose_idx:] - traj2.poses = traj2.poses[first_pose_idx:] - - # Find the one that is at a higher frame rate - # Leaves us with traj1 being the one with the higher frame rate - swapped = False - traj_1_dt = (traj1.stamps[-1] - traj1.stamps[0]).to_sec() / len(traj1.stamps) - traj_2_dt = (traj2.stamps[-1] - traj2.stamps[0]).to_sec() / len(traj2.stamps) - if traj_1_dt > traj_2_dt: - traj1, traj2 = traj2, traj1 - swapped = True - - # Align the two trajectories by subsampling keeping traj1 stamps - traj1_idx = 0 - traj1_stamps = [] - traj1_poses = [] - for i, stamp in enumerate(traj2.stamps): - while traj1_idx < len(traj1) - 1 and traj1.stamps[traj1_idx] < stamp: - traj1_idx += 1 - - # go back one if we overshot - if _check_overstep(traj1.stamps, stamp, traj1_idx): - traj1_idx -= 1 - - traj1_stamps.append(traj1.stamps[traj1_idx]) - traj1_poses.append(traj1.poses[traj1_idx]) - - if traj1_idx >= len(traj1) - 1: - traj2.stamps = traj2.stamps[: i + 1] - traj2.poses = traj2.poses[: i + 1] - break - - traj1.stamps = traj1_stamps - traj1.poses = traj1_poses - - if swapped: - traj1, traj2 = traj2, traj1 - - -def align_poses(traj: Trajectory, gt: Trajectory): - """Transforms the first to have to same origin as the second. - - Operates in place and assumes the trajectories already have their stamps aligned. - - Args: - traj (Trajectory): Trajectory to be aligned - gt (Trajectory): _description_ - """ - imu_o_T_imu_0 = traj.poses[0] - gt_o_T_imu_0 = gt.poses[0] - gt_o_T_imu_o = gt_o_T_imu_0 * imu_o_T_imu_0.inverse() - - traj.poses = [gt_o_T_imu_o * pose for pose in traj.poses] - - -# ------------------------- Methods for computing metrics ------------------------- # -class MetricKind(StrEnum): - mean = auto() - median = auto() - sse = auto() - - -@dataclass(kw_only=True) -class Metric: - trans: float - rot: float - - -@dataclass(kw_only=True) -class Error: - # Shape: (n,) - trans: np.ndarray - rot: np.ndarray - - def summarize(self, metric: MetricKind) -> Metric: - match metric: - case MetricKind.mean: - return self.mean() - case MetricKind.median: - return self.median() - case MetricKind.sse: - return self.sse() - - def mean(self) -> Metric: - return Metric(rot=self.rot.mean(), trans=self.trans.mean()) - - def sse(self) -> Metric: - length = len(self.rot) - return Metric( - rot=float(np.sqrt(self.rot @ self.rot / length)), - trans=float(np.sqrt(self.trans @ self.trans / length)), - ) - - def median(self) -> Metric: - return Metric( - rot=cast(float, np.median(self.rot)), - trans=cast(float, np.median(self.trans)), - ) - - -class ExperimentResults: - metadata: dict - stamps: list[Stamp] - poses: list[SE3] - gts: list[SE3] - - def __init__(self, gt_og: Trajectory, traj: Trajectory): - self.metadata = traj.metadata - - gt = deepcopy(gt_og) - align_stamps(traj, gt) - align_poses(traj, gt) - - self.stamps = traj.stamps - self.poses = traj.poses - self.gts = gt.poses - - def __len__(self) -> int: - return len(self.stamps) - - @staticmethod - def _compute_metric(gts: list[SE3], poses: list[SE3]) -> Error: - assert len(gts) == len(poses) - - error_t = np.zeros(len(gts)) - error_r = np.zeros(len(gts)) - for i, (gt, pose) in enumerate(zip(gts, poses)): - delta = gt.inverse() * pose - error_t[i] = np.sqrt(delta.trans @ delta.trans) # type: ignore - r_diff = delta.rot.log() - error_r[i] = np.sqrt(r_diff @ r_diff) * 180 / np.pi # type: ignore - - return Error(rot=error_r, trans=error_t) - - def ate(self) -> Error: - return self._compute_metric(self.gts, self.poses) - - def rte(self, window: int = 100) -> Error: - if window <= 0: - raise ValueError("Window size must be positive") - - window_deltas_poses = [] - window_deltas_gts = [] - for i in range(len(self.gts) - window): - window_deltas_poses.append(self.poses[i].inverse() * self.poses[i + window]) - window_deltas_gts.append(self.gts[i].inverse() * self.gts[i + window]) - - return self._compute_metric(window_deltas_gts, window_deltas_poses) - - def dict_diff(dicts: Sequence[dict]) -> list[str]: """Compute which values are different between a list of dictionaries. @@ -235,12 +45,13 @@ def eval_dataset( visualize: bool, sort: Optional[str], window_size: int, - metric: MetricKind, + metric: stats.MetricKind, + length: Optional[int], ): # Load all trajectories trajectories = [] for file_path in dir.glob("*.csv"): - traj = Trajectory.load_experiment(file_path) + traj = Trajectory.from_experiment(file_path) trajectories.append(traj) gt_list: list[Trajectory] = [] @@ -269,7 +80,7 @@ def eval_dataset( str(dir), spawn=False, ) - rr.connect_tcp("0.0.0.0:9876") + rr.connect_grpc() rr.log( "gt", convert(gt_og, color=[0, 0, 255]), @@ -295,16 +106,21 @@ def eval_dataset( for pipeline, trajs in grouped_trajs.items(): # Iterate over each for traj in trajs: - exp = ExperimentResults(gt_og, traj) - ate = exp.ate().summarize(metric) - rte = exp.rte(window_size).summarize(metric) + traj_aligned, gt_aligned = stats.align(traj, gt_og) + if length is not None and len(traj_aligned) > length: + traj_aligned.stamps = traj_aligned.stamps[:length] + traj_aligned.poses = traj_aligned.poses[:length] + gt_aligned.stamps = gt_aligned.stamps[:length] + gt_aligned.poses = gt_aligned.poses[:length] + ate = stats.ate(traj_aligned, gt_aligned).summarize(metric) + rte = stats.rte(traj_aligned, gt_aligned, window_size).summarize(metric) r = { "name": traj.metadata["name"], - "ATEt": ate.trans, - "ATEr": ate.rot, "RTEt": rte.trans, "RTEr": rte.rot, - "length": len(exp), + "ATEt": ate.trans, + "ATEr": ate.rot, + "length": len(traj_aligned), } r.update({k: traj.metadata.get(k, "--") for k in keys_to_print}) results.append(r) @@ -363,14 +179,19 @@ def eval( ), ] = 100, metric: Annotated[ - MetricKind, + stats.MetricKind, typer.Option( "--metric", "-m", help="Metric to use for ATE/RTE computation. Defaults to sse.", - case_sensitive=False, ), - ] = MetricKind.sse, + ] = stats.MetricKind.sse, + length: Annotated[ + Optional[int], + typer.Option( + "-l", "--length", help="Specify subset of trajectory to evaluate." + ), + ] = None, ): """ Evaluate the results of experiments. @@ -378,6 +199,10 @@ def eval( directories_path = [Path(d) for d in directories] + c = Console() + c.print(f"Evaluating RTE over a window of size {window}, using metric {metric}.") + c.print() + # Collect all bottom level directories bottom_level_dirs = [] for directory in directories_path: @@ -386,4 +211,4 @@ def eval( bottom_level_dirs.append(subdir) for d in bottom_level_dirs: - eval_dataset(d, visualize, sort, window, metric) + eval_dataset(d, visualize, sort, window, metric, length) diff --git a/python/evalio/cli/writer.py b/python/evalio/cli/writer.py index dfbd203b..38ffa79f 100644 --- a/python/evalio/cli/writer.py +++ b/python/evalio/cli/writer.py @@ -13,6 +13,12 @@ def save_config( datasets: Sequence[DatasetBuilder], output: Path, ): + # If it's just a file, don't save the entire config file + if output.suffix == ".csv": + return + + print(f"Saving config to {output}") + output.mkdir(parents=True, exist_ok=True) path = output / "config.yaml" @@ -26,9 +32,10 @@ def save_config( class TrajectoryWriter: def __init__(self, path: Path, pipeline: PipelineBuilder, dataset: DatasetBuilder): - path = path / dataset.dataset.full_name - path.mkdir(parents=True, exist_ok=True) - path /= f"{pipeline.name}.csv" + if path.suffix != ".csv": + path = path / dataset.dataset.full_name + path.mkdir(parents=True, exist_ok=True) + path /= f"{pipeline.name}.csv" # write metadata to the header # TODO: Could probably automate this using pyserde somehow @@ -74,6 +81,9 @@ def close(self): def save_gt(output: Path, dataset: DatasetBuilder): + if output.suffix == ".csv": + return + gt = dataset.build().ground_truth() path = output / dataset.dataset.full_name path.mkdir(parents=True, exist_ok=True) diff --git a/python/evalio/datasets/__init__.py b/python/evalio/datasets/__init__.py index 2094a2d9..5df729f1 100644 --- a/python/evalio/datasets/__init__.py +++ b/python/evalio/datasets/__init__.py @@ -8,12 +8,11 @@ from .multi_campus import MultiCampus from .oxford_spires import OxfordSpires -from . import loaders +from .loaders import RawDataIter, RosbagIter __all__ = [ "get_data_dir", "set_data_dir", - "loaders", "Dataset", "DatasetIterator", "BotanicGarden", @@ -24,4 +23,6 @@ "NewerCollege2021", "MultiCampus", "OxfordSpires", + "RawDataIter", + "RosbagIter", ] diff --git a/python/evalio/datasets/base.py b/python/evalio/datasets/base.py index 30b06989..71d54e4d 100644 --- a/python/evalio/datasets/base.py +++ b/python/evalio/datasets/base.py @@ -15,6 +15,7 @@ ) from evalio.utils import print_warning +from typing import Sequence Measurement = Union[ImuMeasurement, LidarMeasurement] @@ -23,65 +24,199 @@ class DatasetIterator(Iterable[Measurement]): - def imu_iter(self) -> Iterator[ImuMeasurement]: ... + """This is the base class for iterating over datasets. - def lidar_iter(self) -> Iterator[LidarMeasurement]: ... + This class is the main interface used to iterate over the dataset's measurements. + It provides an interface for iterating over IMU and Lidar measurements, as well as all measurements interleaved. + This allows for standardizing access to loading data, while allowing for loading parameters in [Dataset][evalio.datasets.Dataset] without having to load the data. + Generally, will be created by the [Dataset.data_iter][evalio.datasets.Dataset.data_iter] method. + """ + + def imu_iter(self) -> Iterator[ImuMeasurement]: + """Main interface for iterating over IMU measurements. + + Yields: + Iterator[ImuMeasurement]: Iterator of IMU measurements. + """ + ... + + def lidar_iter(self) -> Iterator[LidarMeasurement]: + """Main interface for iterating over Lidar measurements. + + Yields: + Iterator[LidarMeasurement]: Iterator of Lidar measurements. + """ + ... + + def __iter__(self) -> Iterator[Measurement]: + """Main interface for iterating over all measurements. + + Yields: + Iterator[Measurement]: Iterator of all measurements (IMU and Lidar). + """ - def __iter__(self) -> Iterator[Measurement]: ... + ... # Return the number of lidar scans - def __len__(self) -> int: ... + def __len__(self) -> int: + """Number of lidar scans. + + Returns: + int: Number of lidar scans. + """ + ... class Dataset(StrEnum): + """The base class for all datasets. + + This class provides an interface for loading datasets, including loading parameters and iterating over measurements. + All datasets are string enums, where each enum member represents a trajectory sequence in the dataset. + """ + # ------------------------- For loading data ------------------------- # - def data_iter(self) -> DatasetIterator: ... + def data_iter(self) -> DatasetIterator: + """ + Provides an iterator over the dataset's measurements. + + Returns: + DatasetIterator: An iterator that yields measurements from the dataset. + """ + ... # Return the ground truth in the ground truth frame - def ground_truth_raw(self) -> Trajectory: ... + def ground_truth_raw(self) -> Trajectory: + """ + Retrieves the raw ground truth trajectory, as represented in the ground truth frame. + + Returns: + Trajectory: The raw ground truth trajectory data. + """ + ... # ------------------------- For loading params ------------------------- # - def imu_T_lidar(self) -> SE3: ... + def imu_T_lidar(self) -> SE3: + """Returns the transformation from IMU to Lidar frame. - def imu_T_gt(self) -> SE3: ... + Returns: + SE3: Transformation from IMU to Lidar frame. + """ + ... - def imu_params(self) -> ImuParams: ... + def imu_T_gt(self) -> SE3: + """Retrieves the transformation from IMU to ground truth frame. - def lidar_params(self) -> LidarParams: ... + Returns: + SE3: Transformation from IMU to ground truth frame. + """ + ... - def files(self) -> list[str]: ... + def imu_params(self) -> ImuParams: + """Specifies the parameters of the IMU. + + Returns: + ImuParams: Parameters of the IMU. + """ + ... + + def lidar_params(self) -> LidarParams: + """Specifies the parameters of the Lidar. + + Returns: + LidarParams: Parameters of the Lidar. + """ + ... + + def files(self) -> Sequence[str | Path]: + """Return list of files required to run this dataset. + + If a returned type is a Path, it will be checked as is. If it is a string, it will be prepended with [folder][evalio.datasets.Dataset.folder]. + + Returns: + list[str]: _description_ + """ + ... # ------------------------- Optional dataset info ------------------------- # @staticmethod def url() -> str: + """Webpage with the dataset information. + + Returns: + str: URL of the dataset webpage. + """ return "-" def environment(self) -> str: + """Environment where the dataset was collected. + + Returns: + str: Environment where the dataset was collected. + """ return "-" def vehicle(self) -> str: + """Vehicle used to collect the dataset. + + Returns: + str: Vehicle used to collect the dataset. + """ return "-" - # ------------------------- Optional overrides ------------------------- # - # Optional method + def quick_len(self) -> Optional[int]: + """Hardcoded number of lidar scans in the dataset, rather than computing by loading all the data (slow). + + Returns: + Optional[int]: Number of lidar scans in the dataset. None if not available. + """ + return None + def download(self) -> None: + """Method to download the dataset. + + Completely optional to implement, although most datasets do. + + Raises: + NotImplementedError: If not implemented. + """ raise NotImplementedError("Download not implemented") # TODO: This would match better as a "classproperty", but not will involve some work @classmethod def dataset_name(cls) -> str: + """Name of the dataset, in snake case. + + This is the name that will be used when parsing directly from a string. Currently is automatically generated from the class name, but can be overridden. + + Returns: + str: _description_ + """ return pascal_to_snake(cls.__name__) # ------------------------- Helpers that wrap the above ------------------------- # def is_downloaded(self) -> bool: + """Verify if the dataset is downloaded. + + Returns: + bool: True if the dataset is downloaded, False otherwise. + """ self._warn_default_dir() for f in self.files(): - if not (self.folder / f).exists(): - return False + if isinstance(f, str): + if not (self.folder / f).exists(): + return False + else: + if not f.exists(): + return False return True def ground_truth(self) -> Trajectory: + """Get the ground truth trajectory in the **IMU** frame, rather than the ground truth frame as returned in [ground_truth_raw][evalio.datasets.Dataset.ground_truth_raw]. + + Returns: + Trajectory: The ground truth trajectory in the IMU frame. + """ gt_traj = self.ground_truth_raw() gt_T_imu = self.imu_T_gt().inverse() @@ -111,24 +246,70 @@ def _warn_default_dir(cls): # ------------------------- Helpers that leverage from the iterator ------------------------- # def __len__(self) -> int: + """Return the number of lidar scans. + + If quick_len is available, it will be used. Otherwise, it will load the entire dataset to get the length. + + Returns: + int: Number of lidar scans. + """ + if (length := self.quick_len()) is not None: + return length + + self._fail_not_downloaded() return self.data_iter().__len__() def __iter__(self) -> Iterator[Measurement]: # type: ignore + """Main interface for iterating over measurements of all types. + + Returns: + Iterator[Measurement]: Iterator of all measurements (IMU and Lidar). + """ self._fail_not_downloaded() return self.data_iter().__iter__() def imu(self) -> Iterable[ImuMeasurement]: + """Iterate over just IMU measurements. + + Returns: + Iterable[ImuMeasurement]: Iterator of IMU measurements. + """ self._fail_not_downloaded() return self.data_iter().imu_iter() def lidar(self) -> Iterable[LidarMeasurement]: + """Iterate over just Lidar measurements. + + Returns: + Iterable[LidarMeasurement]: Iterator of Lidar measurements. + """ self._fail_not_downloaded() return self.data_iter().lidar_iter() def get_one_lidar(self, idx: int = 0) -> LidarMeasurement: + """Get a single Lidar measurement. + + Note, this can be expensive to compute, as it will iterate over the entire dataset until it finds the measurement. + + Args: + idx (int, optional): Index of measurement to get. Defaults to 0. + + Returns: + LidarMeasurement: The Lidar measurement at the given index. + """ return next(islice(self.lidar(), idx, idx + 1)) def get_one_imu(self, idx: int = 0) -> ImuMeasurement: + """Get a single IMU measurement. + + Note, this can be expensive to compute, as it will iterate over the entire dataset until it finds the measurement. + + Args: + idx (int, optional): Index of measurement to get. Defaults to 0. + + Returns: + ImuMeasurement: The IMU measurement at the given index. + """ return next(islice(self.imu(), idx, idx + 1)) # ------------------------- Misc name helpers ------------------------- # @@ -137,22 +318,50 @@ def __str__(self): @property def seq_name(self) -> str: + """Name of the sequence, in snake case. + + Returns: + str: Name of the sequence. + """ return self.value @property def full_name(self) -> str: + """Full name of the dataset, including the dataset name and sequence name. + + Example: "dataset_name/sequence_name" + + Returns: + str: Full name of the dataset. + """ return f"{self.dataset_name()}/{self.seq_name}" @classmethod def sequences(cls) -> list["Dataset"]: + """All sequences in the dataset. + + Returns: + list[Dataset]: List of all sequences in the dataset. + """ return list(cls.__members__.values()) @property def folder(self) -> Path: + """The folder in the global dataset directory where this dataset is stored. + + Returns: + Path: Path to the dataset folder. + """ global _DATA_DIR return _DATA_DIR / self.full_name def size_on_disk(self) -> Optional[float]: + """Shows the size of the dataset on disk, in GB. + + Returns: + Optional[float]: Size of the dataset on disk, in GB. None if the dataset is not downloaded. + """ + if not self.is_downloaded(): return None else: @@ -193,8 +402,10 @@ def pascal_to_snake(identifier): # ------------------------- Helpers ------------------------- # def set_data_dir(directory: Path): - """ - Set the location where datasets are stored. This will be used to store the downloaded data. + """Set the global location where datasets are stored. This will be used to store the downloaded data. + + Args: + directory (Path): Directory """ global _DATA_DIR, _WARNED _DATA_DIR = directory @@ -202,8 +413,10 @@ def set_data_dir(directory: Path): def get_data_dir() -> Path: - """ - Get the data directory for the dataset. This will be used to store the downloaded data. + """Get the global data directory. This will be used to store the downloaded data. + + Returns: + Path: Directory where datasets are stored. """ global _DATA_DIR return _DATA_DIR diff --git a/python/evalio/datasets/botanic_garden.py b/python/evalio/datasets/botanic_garden.py index ad37485d..a9b4a7fe 100644 --- a/python/evalio/datasets/botanic_garden.py +++ b/python/evalio/datasets/botanic_garden.py @@ -20,8 +20,18 @@ DatasetIterator, ) +from pathlib import Path +from typing import Sequence, Optional + class BotanicGarden(Dataset): + """Dataset taken from a botanical garden, specifically for testing unstructured environments. Ground truth is gathered using a survey grade lidar. + + Note, there is no automatic downloader for this dataset due to being uploaded on onedrive. Data can be downloaded manually and placed in the correct folder in `EVALIO_DATA`. + + Additionally, we only include the public datasets here; more are potentially available upon request. + """ + b1005_00 = auto() b1005_01 = auto() b1005_07 = auto() @@ -52,7 +62,7 @@ def ground_truth_raw(self) -> Trajectory: else: filename = f"{self.seq_name[1:]}_GT_output.txt" - return Trajectory.load_csv( + return Trajectory.from_csv( self.folder / filename, ["sec", "x", "y", "z", "qx", "qy", "qz", "qw"], delimiter=" ", @@ -117,7 +127,7 @@ def lidar_params(self) -> LidarParams: model="VLP-16", ) - def files(self) -> list[str]: + def files(self) -> Sequence[str | Path]: out = [f"{self.seq_name[1:]}.bag", f"{self.seq_name[1:]}_GT_output.txt"] if self.seq_name == "b1008_03": out[1] = f"{self.seq_name[1:]}_gt_output.txt" @@ -134,3 +144,14 @@ def environment(self) -> str: def vehicle(self) -> str: return "ATV" + + def quick_len(self) -> Optional[int]: + return { + "b1005_00": 5790, + "b1005_01": 4746, + "b1005_07": 5474, + "b1006_01": 7445, + "b1008_03": 6320, + "b1018_00": 1466, + "b1018_13": 2071, + }[self.seq_name] diff --git a/python/evalio/datasets/enwide.py b/python/evalio/datasets/enwide.py index 42e71a11..a0bccca3 100644 --- a/python/evalio/datasets/enwide.py +++ b/python/evalio/datasets/enwide.py @@ -22,6 +22,8 @@ DatasetIterator, ) +from typing import Sequence, cast, Optional + # https://github.com/pytorch/vision/blob/fc746372bedce81ecd53732ee101e536ae3afec1/torchvision/datasets/utils.py#L27 def _urlretrieve(url: str, filename: Path, chunk_size: int = 1024 * 32) -> None: @@ -40,6 +42,11 @@ def _urlretrieve(url: str, filename: Path, chunk_size: int = 1024 * 32) -> None: class EnWide(Dataset): + """Dataset taken in purposely degenerate locations such as a field, intersections, tunnels, and runways. All data comes directly from the Ouster unit. + + Note, this dataset does not have ground truth orientation, only ground truth positional values taken from a Leica MS60 Prism. + """ + field_d = auto() field_s = auto() intersection_d = auto() @@ -67,7 +74,7 @@ def data_iter(self) -> DatasetIterator: ) def ground_truth_raw(self) -> Trajectory: - return Trajectory.load_csv( + return Trajectory.from_csv( self.folder / f"gt-{self.seq_name}.csv", ["sec", "x", "y", "z", "qx", "qy", "qz", "qw"], delimiter=" ", @@ -139,7 +146,7 @@ def vehicle(self) -> str: return "Handheld" # ------------------------- For downloading ------------------------- # - def files(self) -> list[str]: + def files(self) -> Sequence[str | Path]: return { "intersection_s": [ "2023-08-09-16-19-09-intersection_s.bag", @@ -166,7 +173,7 @@ def files(self) -> list[str]: }[self.seq_name] def download(self): - bag_file, gt_file = self.files() + bag_file, gt_file = cast(list[str], self.files()) url = f"http://robotics.ethz.ch/~asl-datasets/2024_ICRA_ENWIDE/{self.seq_name}/" @@ -176,3 +183,17 @@ def download(self): _urlretrieve(url + gt_file, self.folder / gt_file) if not (self.folder / bag_file).exists(): _urlretrieve(url + bag_file, self.folder / bag_file) + + def quick_len(self) -> Optional[int]: + return { + "field_d": 1477, + "field_s": 1671, + "intersection_d": 1828, + "intersection_s": 1997, + "katzensee_d": 858, + "katzensee_s": 1620, + "runway_d": 1902, + "runway_s": 2238, + "tunnel_d": 1189, + "tunnel_s": 2380, + }[self.seq_name] diff --git a/python/evalio/datasets/helipr.py b/python/evalio/datasets/helipr.py index 2b16b970..e26ad8d0 100644 --- a/python/evalio/datasets/helipr.py +++ b/python/evalio/datasets/helipr.py @@ -17,12 +17,20 @@ import os +from pathlib import Path +from typing import Sequence, Optional + """ Note, we do everything based off of the Ouster Lidar, mounted at the top of the vehicle. """ class HeLiPR(Dataset): + """Self-driving car dataset taken in urban environments. Ground truth is generated using filtering of an RTK-GNSS system. + + The vehicle had multiple lidar sensors mounted; we utilize the high resolution Ouster at the top of the vehicle. + """ + # Had to remove a couple of them due to not having imu data # kaist_04 = auto() kaist_05 = auto() @@ -66,7 +74,7 @@ def lidar_iter(): ) def ground_truth_raw(self) -> Trajectory: - return Trajectory.load_csv( + return Trajectory.from_csv( self.folder / "Ouster_gt.txt", ["nsec", "x", "y", "z", "qx", "qy", "qz", "qw"], delimiter=" ", @@ -133,7 +141,7 @@ def vehicle(self) -> str: return "Car" # ------------------------- For downloading ------------------------- # - def files(self) -> list[str]: + def files(self) -> Sequence[str | Path]: return ["Ouster", "Ouster_gt.txt", "xsens_imu.csv"] def download(self): @@ -190,3 +198,13 @@ def download(self): tar.extractall(path=self.folder) print("Removing tar file...") tar_file.unlink() + + def quick_len(self) -> Optional[int]: + return { + "kaist_05": 12477, + "kaist_06": 12152, + "dcc_05": 10810, + "dcc_06": 10742, + "riverside_05": 8551, + "riverside_06": 11948, + }[self.seq_name] diff --git a/python/evalio/datasets/hilti_2022.py b/python/evalio/datasets/hilti_2022.py index 6d66e272..42ca7fc7 100644 --- a/python/evalio/datasets/hilti_2022.py +++ b/python/evalio/datasets/hilti_2022.py @@ -23,6 +23,8 @@ DatasetIterator, ) +from typing import Sequence, cast, Optional + # https://github.com/pytorch/vision/blob/fc746372bedce81ecd53732ee101e536ae3afec1/torchvision/datasets/utils.py#L27 def _urlretrieve(url: str, filename: Path, chunk_size: int = 1024 * 32) -> None: @@ -41,6 +43,8 @@ def _urlretrieve(url: str, filename: Path, chunk_size: int = 1024 * 32) -> None: class Hilti2022(Dataset): + """Sequences with ground truth taken from the Hilti 2022 SLAM Challenge, mostly taken from indoors.""" + construction_upper_level_1 = auto() construction_upper_level_2 = auto() construction_upper_level_3 = auto() @@ -65,9 +69,8 @@ def data_iter(self) -> DatasetIterator: ) def ground_truth_raw(self) -> Trajectory: - # TODO: Update the path to the ground truth file _, gt = self.files() - return Trajectory.load_csv( + return Trajectory.from_csv( self.folder / gt, ["sec", "x", "y", "z", "qx", "qy", "qz", "qw"], delimiter=" ", @@ -122,7 +125,7 @@ def vehicle(self) -> str: return "Handheld" # ------------------------- For downloading ------------------------- # - def files(self) -> list[str]: + def files(self) -> Sequence[str | Path]: filename = { "construction_upper_level_1": "exp04_construction_upper_level", "construction_upper_level_2": "exp05_construction_upper_level_2", @@ -142,7 +145,7 @@ def files(self) -> list[str]: return [bag_file, gt_file] def download(self): - bag_file, gt_file = self.files() + bag_file, gt_file = cast(list[str], self.files()) url = "https://tp-public-facing.s3.eu-north-1.amazonaws.com/Challenges/2022/" @@ -154,3 +157,13 @@ def download(self): if not (self.folder / bag_file).exists(): print(f"Downloading {bag_file}") _urlretrieve(url + bag_file, self.folder / bag_file) + + def quick_len(self) -> Optional[int]: + return { + "construction_upper_level_1": 1258, + "construction_upper_level_2": 1248, + "construction_upper_level_3": 1508, + "basement_2": 740, + "attic_to_upper_gallery_2": 2003, + "corridor_lower_gallery_2": 1094, + }[self.seq_name] diff --git a/python/evalio/datasets/loaders.py b/python/evalio/datasets/loaders.py index c1ca9fea..91fd6a02 100644 --- a/python/evalio/datasets/loaders.py +++ b/python/evalio/datasets/loaders.py @@ -33,6 +33,7 @@ # ------------------------- Iterator over a rosbag ------------------------- # # Various properties that a pointcloud may have - we iterate over them +# TODO: Nest these into LidarFormatParams or something class LidarStamp(StrEnum): Start = auto() End = auto() @@ -65,6 +66,12 @@ class LidarFormatParams: class RosbagIter(DatasetIterator): + """An iterator for loading from rosbag files. + + This is a wrapper around the rosbags library, with some niceties for converting ros PointCloud2 messages to a standardized format. + Has identical methods to [DatasetIterator][evalio.datasets.DatasetIterator]. + """ + def __init__( self, path: Path, @@ -77,6 +84,20 @@ def __init__( lidar_format: Optional[LidarFormatParams] = None, custom_col_func: Optional[Callable[[LidarMeasurement], None]] = None, ): + """ + Args: + path (Path): Location of rosbag file(s). If a directory is passed, all .bag files in the directory will be loaded. + lidar_topic (str): Name of lidar topic. + imu_topic (str): Name of imu topic. + lidar_params (LidarParams): Lidar parameters, can be gotten from [lidar_params][evalio.datasets.Dataset.lidar_params]. + is_mcap (bool, optional): If an mcap file, will not try to glob over all rosbags. Defaults to False. + lidar_format (Optional[LidarFormatParams], optional): Various parameters for how lidar data is stored. If not specified, most will try to be inferred. We strongly recommend setting this to ensure data is standardized properly. Defaults to None. + custom_col_func (Optional[Callable[[LidarMeasurement], None]], optional): Function to put the point cloud in row major format. Will generally not be needed, except for strange default orderings. Defaults to None. + + Raises: + FileNotFoundError: If the path is a directory and no .bag files are found. + ValueError: If the lidar or imu topic is not found in the bag file. + """ self.lidar_topic = lidar_topic self.imu_topic = imu_topic self.lidar_params = lidar_params @@ -263,6 +284,12 @@ def _lidar_conversion(self, msg: Any) -> LidarMeasurement: # ------------------------- Flexible Iterator for Anything ------------------------- # class RawDataIter(DatasetIterator): + """An iterator for loading from python iterables. + + Interleaves imu and lidar iterables. Allows for arbitrary data to be loaded and presented in a consistent manner for the base [Dataset][evalio.datasets.Dataset] class. + Has identical methods to [DatasetIterator][evalio.datasets.DatasetIterator]. + """ + T = TypeVar("T", ImuMeasurement, LidarMeasurement) def __init__( @@ -271,6 +298,28 @@ def __init__( iter_imu: Iterator[ImuMeasurement], num_lidar: int, ): + """ + Args: + iter_lidar (Iterator[LidarMeasurement]): An iterator over LidarMeasurement + iter_imu (Iterator[ImuMeasurement]): An iterator over ImuMeasurement + num_lidar (int): The number of lidar measurements + + ``` py + from evalio.datasets.loaders import RawDataIter + from evalio.types import ImuMeasurement, LidarMeasurement, Stamp + import numpy as np + + # Create some fake data + imu_iter = ( + ImuMeasurement(Stamp.from_sec(i), np.zeros(3), np.zeros(3)) + for i in range(10) + ) + lidar_iter = (LidarMeasurement(Stamp.from_sec(i + 0.1)) for i in range(10)) + + # Create the iterator + rawdata = RawDataIter(imu_iter, lidar_iter, 10) + ``` + """ self.iter_lidar = iter_lidar self.iter_imu = iter_imu self.num_lidar = num_lidar diff --git a/python/evalio/datasets/multi_campus.py b/python/evalio/datasets/multi_campus.py index 00164be2..84f4c1b0 100644 --- a/python/evalio/datasets/multi_campus.py +++ b/python/evalio/datasets/multi_campus.py @@ -21,8 +21,16 @@ DatasetIterator, ) +from pathlib import Path +from typing import Sequence, Optional + class MultiCampus(Dataset): + """Data taken from a variety of campus (KTH, NTU, TUHH) in Asia and Europe at different seasons, at day and night, and with an ATV and handheld platform. + + Ground truth was measured using a continuous optimization of lidar scans matched against a laser scanner map. + """ + ntu_day_01 = auto() ntu_day_02 = auto() ntu_day_10 = auto() @@ -73,7 +81,7 @@ def data_iter(self) -> DatasetIterator: raise ValueError(f"Unknown sequence: {self.seq_name}") def ground_truth_raw(self) -> Trajectory: - return Trajectory.load_csv( + return Trajectory.from_csv( self.folder / "pose_inW.csv", ["num", "t", "x", "y", "z", "qx", "qy", "qz", "qw"], skip_lines=1, @@ -213,7 +221,7 @@ def vehicle(self) -> str: raise ValueError(f"Unknown sequence: {self.seq_name}") # ------------------------- For downloading ------------------------- # - def files(self) -> list[str]: + def files(self) -> Sequence[str | Path]: if "ntu" in self.seq_name: beams = 128 imu = "vn100" @@ -299,3 +307,25 @@ def download(self): gdown.download(id=gt_url, output=folder, resume=True) gdown.download(id=ouster_url, output=folder, resume=True) gdown.download(id=imu_url, output=folder, resume=True) + + def quick_len(self) -> Optional[int]: + return { + "ntu_day_01": 6024, + "ntu_day_02": 2288, + "ntu_day_10": 3248, + "ntu_night_04": 2966, + "ntu_night_08": 4668, + "ntu_night_13": 2338, + "kth_day_06": 8911, + "kth_day_09": 7670, + "kth_day_10": 6155, + "kth_night_01": 9690, + "kth_night_04": 7465, + "kth_night_05": 6653, + "tuhh_day_02": 5004, + "tuhh_day_03": 8395, + "tuhh_day_04": 1879, + "tuhh_night_07": 4446, + "tuhh_night_08": 7091, + "tuhh_night_09": 1849, + }[self.seq_name] diff --git a/python/evalio/datasets/newer_college_2020.py b/python/evalio/datasets/newer_college_2020.py index a0bb4b47..3a459982 100644 --- a/python/evalio/datasets/newer_college_2020.py +++ b/python/evalio/datasets/newer_college_2020.py @@ -19,8 +19,18 @@ DatasetIterator, ) +from pathlib import Path +from typing import Sequence, Optional + class NewerCollege2020(Dataset): + """Dataset taken from outdoor Oxford Campus. Ground truth is generated using ICP matching against a laser scanner. + + Note, there have been some reports that the laser scanner and data were collected months apart, which may have caused some inaccuracies in the ground truth data. + + There are two IMUs on the handheld device, but the realsense IMU is not time-synced with the lidar data. Therefore, we utilize the Ouster IMU data instead. + """ + short_experiment = auto() long_experiment = auto() quad_with_dynamics = auto() @@ -46,13 +56,13 @@ def data_iter(self) -> DatasetIterator: def ground_truth_raw(self) -> Trajectory: # For some reason bag parkland mound is different if self.seq_name == "parkland_mound": - return Trajectory.load_csv( + return Trajectory.from_csv( self.folder / "registered_poses.csv", ["sec", "x", "y", "z", "qx", "qy", "qz", "qw"], delimiter=" ", ) - return Trajectory.load_csv( + return Trajectory.from_csv( self.folder / "registered_poses.csv", ["sec", "nsec", "x", "y", "z", "qx", "qy", "qz", "qw"], ) @@ -107,7 +117,7 @@ def vehicle(self) -> str: return "Handheld" # ------------------------- For downloading ------------------------- # - def files(self) -> list[str]: + def files(self) -> Sequence[str | Path]: return { "dynamic_spinning": [ "rooster_2020-07-10-09-23-18_0.bag", @@ -178,3 +188,10 @@ def download(self): self.folder.mkdir(parents=True, exist_ok=True) gdown.download(id=gt_url, output=f"{self.folder}{os.sep}", resume=True) gdown.download_folder(id=folder_id, output=str(self.folder), resume=True) + + def quick_len(self) -> Optional[int]: + # TODO: Missing some values here + return { + "short_experiment": 15302, + "long_experiment": 26560, + }.get(self.seq_name) diff --git a/python/evalio/datasets/newer_college_2021.py b/python/evalio/datasets/newer_college_2021.py index 74062d43..d58f8c25 100644 --- a/python/evalio/datasets/newer_college_2021.py +++ b/python/evalio/datasets/newer_college_2021.py @@ -19,15 +19,18 @@ import os -""" -As a reference, we use the built in Ouster IMU instead of the alphasense one -Extrinsics are more likely to be accurate -Also, the alphasense IMU (Bosch BMI085) has fairly similar specs to the Ouster one (ICM-20948) - -""" +from pathlib import Path +from typing import Sequence, Optional class NewerCollege2021(Dataset): + """Dataset outdoors on oxford campus with a handheld device consisting of an alphasense core and a Ouster lidar. + Ground truth is captured ICP matching against a laser scanner map. + + Note there are two IMUs present; we utilize the Ouster IMU (ICM-20948)) instead of the alphasense one (Bosch BMI085). + We expect the Ouster IMU to have more accurate extrinsics and the specs between the two IMUs are fairly similar. + """ + quad_easy = auto() quad_medium = auto() quad_hard = auto() @@ -55,7 +58,7 @@ def data_iter(self) -> DatasetIterator: def ground_truth_raw(self) -> Trajectory: gt_file = self.files()[-1] - return Trajectory.load_csv( + return Trajectory.from_csv( self.folder / gt_file, ["sec", "nsec", "x", "y", "z", "qx", "qy", "qz", "qw"], ) @@ -110,7 +113,7 @@ def vehicle(self) -> str: return "Handheld" # ------------------------- For downloading ------------------------- # - def files(self) -> list[str]: + def files(self) -> Sequence[str | Path]: # parse ground truth file if "maths" in self.seq_name: difficulty = self.seq_name.split("_")[1] @@ -209,3 +212,16 @@ def download(self): gdown.download(id=gt_ids, output=f"{self.folder}{os.sep}", resume=True) for bid in bag_ids: gdown.download(id=bid, output=f"{self.folder}{os.sep}", resume=True) + + def quick_len(self) -> Optional[int]: + return { + "quad_easy": 1991, + "quad_medium": 1910, + "quad_hard": 1880, + "stairs": 1190, + "cloister": 2788, + "park": 15722, + "maths_easy": 2160, + "maths_medium": 1770, + "maths_hard": 2440, + }[self.seq_name] diff --git a/python/evalio/datasets/oxford_spires.py b/python/evalio/datasets/oxford_spires.py index 940cc343..0db40f35 100644 --- a/python/evalio/datasets/oxford_spires.py +++ b/python/evalio/datasets/oxford_spires.py @@ -19,14 +19,18 @@ ) import os - -""" -Note, we skip over a number of trajectories due to missing ground truth data. -https://docs.google.com/document/d/1RS9QSOP4rC7BWoCD6EYUCm9uV_oMkfa3b61krn9OLG8/edit?tab=t.0 -""" +from pathlib import Path +from typing import Sequence, Optional class OxfordSpires(Dataset): + """Dataset taken both indoors and outdoors on the Oxford campus. + + Note, we skip over a number of trajectories due to [missing ground truth data](https://docs.google.com/document/d/1RS9QSOP4rC7BWoCD6EYUCm9uV_oMkfa3b61krn9OLG8/edit?tab=t.0). + + Additionally, some of the ground truth has poses within a few milliseconds of each other - we skip over any ground truth values within 10 milliseconds of each other. + """ + blenheim_palace_01 = auto() blenheim_palace_02 = auto() blenheim_palace_05 = auto() @@ -60,7 +64,7 @@ def data_iter(self) -> DatasetIterator: def ground_truth_raw(self) -> Trajectory: # Some of these are within a few milliseconds of each other # skip over ones that are too close - traj = Trajectory.load_csv( + traj = Trajectory.from_csv( self.folder / "gt-tum.txt", ["sec", "x", "y", "z", "qx", "qy", "qz", "qw"], delimiter=" ", @@ -153,7 +157,7 @@ def vehicle(self) -> str: return "Backpack" # ------------------------- For downloading ------------------------- # - def files(self) -> list[str]: + def files(self) -> Sequence[str | Path]: return { "christ_church_02": [ "1710754066_2024-03-18-09-27-47_0", @@ -264,3 +268,18 @@ def download(self): self.folder.mkdir(parents=True, exist_ok=True) gdown.download(id=gt_url, output=f"{self.folder}{os.sep}", resume=True) gdown.download_folder(id=folder_id, output=str(self.folder), resume=True) + + def quick_len(self) -> Optional[int]: + # TODO: Missing some of the sequences here, need to figure out multi-folder mcap files + return { + "blenheim_palace_01": 4052, + "blenheim_palace_02": 3674, + "blenheim_palace_05": 3401, + "bodleian_library_02": 5007, + "christ_church_03": 3123, + "christ_church_05": 8007, + "keble_college_02": 3007, + "keble_college_03": 2867, + "observatory_quarter_01": 2894, + "observatory_quarter_02": 2755, + }.get(self.seq_name) diff --git a/python/evalio/rerun.py b/python/evalio/rerun.py index 75ae9c2b..09b3c9ba 100644 --- a/python/evalio/rerun.py +++ b/python/evalio/rerun.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Sequence, overload +from typing import Any, Literal, Optional, Sequence, overload from uuid import uuid4 from evalio.cli.parser import PipelineBuilder @@ -46,7 +46,7 @@ def parse(opts: str) -> "VisArgs": import rerun as rr import rerun.blueprint as rrb - OverrideType = dict[rr.datatypes.EntityPath | str, list[rr.ComponentBatchLike]] + OverrideType = dict[rr.datatypes.EntityPathLike, list[rr.AsComponents]] class RerunVis: # type: ignore def __init__(self, args: VisArgs): @@ -93,7 +93,7 @@ def new_recording(self, dataset: Dataset, pipelines: list[PipelineBuilder]): make_default=True, recording_id=uuid4(), ) - rr.connect_tcp(default_blueprint=self._blueprint(pipelines)) + rr.connect_grpc(default_blueprint=self._blueprint(pipelines)) self.gt = dataset.ground_truth() self.lidar_params = dataset.lidar_params() self.imu_T_lidar = dataset.imu_T_lidar() @@ -174,28 +174,106 @@ def log( # point clouds @overload def convert( - obj: LidarMeasurement, color: Optional[str | list[int]] = None - ) -> rr.Points3D: ... + obj: LidarMeasurement, + color: Optional[Literal["z", "intensity"] | list[int]] = None, + ) -> rr.Points3D: + """Convert a LidarMeasurement to a rerun Points3D. + + Args: + obj (LidarMeasurement): LidarMeasurement to convert. + color (Optional[str | list[int]], optional): Optional color for points. Can be a list of colors, e.g. `[255, 0, 0]` for red, or one of `z` or `intensity`. Defaults to None. + + Returns: + rr.Points3D: LidarMeasurement converted to rerun Points3D. + """ + + ... + @overload def convert( - obj: list[Point], color: Optional[str | list[int]] = None - ) -> rr.Points3D: ... + obj: list[Point], + color: Optional[Literal["z", "intensity"] | list[int]] = None, + ) -> rr.Points3D: + """Convert a list of Points to a rerun Points3D. + + Args: + obj (list[Points]): Points to convert. + color (Optional[str | list[int]], optional): Optional color for points. Can be a list of colors, e.g. `[255, 0, 0]` for red, or one of `z` or `intensity`. Defaults to None. + + Returns: + rr.Points3D: Points converted to rerun Points3D. + """ + ... + @overload - def convert(obj: np.ndarray, color: Optional[np.ndarray] = None) -> rr.Points3D: ... + def convert(obj: np.ndarray, color: Optional[np.ndarray] = None) -> rr.Points3D: + """Convert an (n, 3) numpy array to a rerun Points3D. + + Args: + obj (np.ndarray): LidarMeasurement to convert. + color (Optional[str | list[int]], optional): Optional color for points. Can be a list of colors, e.g. `[255, 0, 0]` for red, or one of `z` or `intensity`. Defaults to None. + + Returns: + rr.Points3D: numpy array converted to rerun Points3D. + """ + ... # trajectories @overload - def convert(obj: list[SE3], color: Optional[list[int]] = None) -> rr.Points3D: ... + def convert(obj: list[SE3], color: Optional[list[int]] = None) -> rr.Points3D: + """Convert a list of SE3 poses to a rerun Points3D. + + Args: + obj (list[SE3]): List of SE3 poses to convert. + color (Optional[list[int]], optional): Optional color for points, as a list of colors, e.g. `[255, 0, 0]` for red. Defaults to None. + + Returns: + rr.Points3D: List of SE3 poses converted to rerun Points3D. + """ + ... + @overload - def convert(obj: Trajectory, color: Optional[list[int]] = None) -> rr.Points3D: ... + def convert(obj: Trajectory, color: Optional[list[int]] = None) -> rr.Points3D: + """Convert a Trajectory a rerun Points3D. + + Args: + obj (Trajectory): Trajectory to convert. + color (Optional[list[int]], optional): Optional color for points, as a list of colors, e.g. `[255, 0, 0]` for red. Defaults to None. + + Returns: + rr.Points3D: Trajectory converted to rerun Points3D. + """ + ... # poses @overload - def convert(obj: SE3) -> rr.Transform3D: ... + def convert(obj: SE3) -> rr.Transform3D: + """Convert a SE3 pose to a rerun Transform3D. + + Args: + obj (SE3): SE3 pose to convert. + + Returns: + rr.Transform3D: SE3 pose converted to rerun Transform3D. + """ + ... def convert( obj: object, color: Optional[Any] = None ) -> rr.Transform3D | rr.Points3D: + """Convert a variety of objects to rerun types. + + Args: + obj (object): Object to convert. Can be a LidarMeasurement, list of Points, numpy array, SE3, or Trajectory. + color (Optional[Any], optional): Optional color to set. See overloads for additional literal options. Defaults to None. + + Raises: + ValueError: If the color pass is invalid. + ValueError: If the object is not an implemented type for conversion. + + Returns: + rr.Transform3D | rr.Points3D: Rerun type. + """ # Handle point clouds if isinstance(obj, LidarMeasurement): color_parsed = None diff --git a/python/evalio/stats.py b/python/evalio/stats.py new file mode 100644 index 00000000..000ca55b --- /dev/null +++ b/python/evalio/stats.py @@ -0,0 +1,283 @@ +from enum import StrEnum, auto +from .types import Stamp, Trajectory, SE3 + +from dataclasses import dataclass + +import numpy as np + +from typing import cast + +from copy import deepcopy + + +def _check_overstep(stamps: list[Stamp], s: Stamp, idx: int) -> bool: + return abs((stamps[idx - 1] - s).to_sec()) < abs((stamps[idx] - s).to_sec()) + + +class MetricKind(StrEnum): + """Simple enum to define the metric to use for summarizing the error. Used in [Error][evalio.stats.Error.summarize].""" + + mean = auto() + """Mean""" + median = auto() + """Median""" + sse = auto() + """Sqrt of Sum of squared errors""" + + +@dataclass(kw_only=True) +class Metric: + """Simple dataclass to hold the resulting metrics. Likely output from [Error][evalio.stats.Error].""" + + trans: float + """translation error in meters""" + rot: float + """rotation error in degrees""" + + +@dataclass(kw_only=True) +class Error: + """ + Dataclass to hold the error between two trajectories. + Generally output from computing [ate][evalio.stats.ate] or [rte][evalio.stats.rte]. + + Contains a (n,) arrays of translation and rotation errors. + """ + + # Shape: (n,) + trans: np.ndarray + """translation error, shape (n,), in meters""" + rot: np.ndarray + """rotation error, shape (n,), in degrees""" + + def summarize(self, metric: MetricKind) -> Metric: + """How to summarize the vector of errors. + + Args: + metric (MetricKind): The metric to use for summarizing the error, + either mean, median, or sse. + + Returns: + Metric: The summarized error + """ + match metric: + case MetricKind.mean: + return self.mean() + case MetricKind.median: + return self.median() + case MetricKind.sse: + return self.sse() + + def mean(self) -> Metric: + """Compute the mean of the errors.""" + return Metric(rot=self.rot.mean(), trans=self.trans.mean()) + + def sse(self) -> Metric: + """Compute the sqrt of sum of squared errors.""" + length = len(self.rot) + return Metric( + rot=float(np.sqrt(self.rot @ self.rot / length)), + trans=float(np.sqrt(self.trans @ self.trans / length)), + ) + + def median(self) -> Metric: + """Compute the median of the errors.""" + return Metric( + rot=cast(float, np.median(self.rot)), + trans=cast(float, np.median(self.trans)), + ) + + +def align( + traj: Trajectory, gt: Trajectory, in_place: bool = False +) -> tuple[Trajectory, Trajectory]: + """Align the trajectories both spatially and temporally. + + The resulting trajectories will be have the same origin as the second ("gt") trajectory. + See [align_poses][evalio.stats.align_poses] and [align_stamps][evalio.stats.align_stamps] for more details. + + Args: + traj (Trajectory): One of the trajectories to align. + gt (Trajectory): The other trajectory to align to. + in_place (bool, optional): If true, the original trajectory will be modified. Defaults to False. + """ + if not in_place: + traj = deepcopy(traj) + gt = deepcopy(gt) + + align_stamps(traj, gt) + align_poses(traj, gt) + + return traj, gt + + +def align_poses(traj: Trajectory, other: Trajectory): + """Align the trajectory in place to another trajectory. Operates in place. + + This results in the current trajectory having an identical first pose to the other trajectory. + Assumes the first pose of both trajectories have the same stamp. + + Args: + traj (Trajectory): The trajectory that will be modified + other (Trajectory): The trajectory to align to. + """ + this = traj.poses[0] + oth = other.poses[0] + delta = oth * this.inverse() + + for i in range(len(traj.poses)): + traj.poses[i] = delta * traj.poses[i] + + +def align_stamps(traj1: Trajectory, traj2: Trajectory): + """Select the closest poses in traj1 and traj2. Operates in place. + + Does this by finding the higher frame rate trajectory and subsampling it to the closest poses of the other one. + Additionally it checks the beginning of the trajectories to make sure they start at about the same stamp. + + Args: + traj1 (Trajectory): One trajectory + traj2 (Trajectory): Other trajectory + """ + # Check if we need to skip poses in traj1 + first_pose_idx = 0 + while traj1.stamps[first_pose_idx] < traj2.stamps[0]: + first_pose_idx += 1 + if _check_overstep(traj1.stamps, traj2.stamps[0], first_pose_idx): + first_pose_idx -= 1 + traj1.stamps = traj1.stamps[first_pose_idx:] + traj1.poses = traj1.poses[first_pose_idx:] + + # Check if we need to skip poses in traj2 + first_pose_idx = 0 + while traj2.stamps[first_pose_idx] < traj1.stamps[0]: + first_pose_idx += 1 + if _check_overstep(traj2.stamps, traj1.stamps[0], first_pose_idx): + first_pose_idx -= 1 + traj2.stamps = traj2.stamps[first_pose_idx:] + traj2.poses = traj2.poses[first_pose_idx:] + + # Find the one that is at a higher frame rate + # Leaves us with traj1 being the one with the higher frame rate + swapped = False + traj_1_dt = (traj1.stamps[-1] - traj1.stamps[0]).to_sec() / len(traj1.stamps) + traj_2_dt = (traj2.stamps[-1] - traj2.stamps[0]).to_sec() / len(traj2.stamps) + if traj_1_dt > traj_2_dt: + traj1, traj2 = traj2, traj1 + swapped = True + + # Align the two trajectories by subsampling keeping traj1 stamps + traj1_idx = 0 + traj1_stamps = [] + traj1_poses = [] + for i, stamp in enumerate(traj2.stamps): + while traj1_idx < len(traj1) - 1 and traj1.stamps[traj1_idx] < stamp: + traj1_idx += 1 + + # go back one if we overshot + if _check_overstep(traj1.stamps, stamp, traj1_idx): + traj1_idx -= 1 + + traj1_stamps.append(traj1.stamps[traj1_idx]) + traj1_poses.append(traj1.poses[traj1_idx]) + + if traj1_idx >= len(traj1) - 1: + traj2.stamps = traj2.stamps[: i + 1] + traj2.poses = traj2.poses[: i + 1] + break + + traj1.stamps = traj1_stamps + traj1.poses = traj1_poses + + if swapped: + traj1, traj2 = traj2, traj1 + + +def _compute_metric(gts: list[SE3], poses: list[SE3]) -> Error: + """Iterate and compute the SE(3) delta between two lists of poses. + + Args: + gts (list[SE3]): One of the lists of poses + poses (list[SE3]): The other list of poses + + Returns: + Error: The computed error + """ + assert len(gts) == len(poses) + + error_t = np.zeros(len(gts)) + error_r = np.zeros(len(gts)) + for i, (gt, pose) in enumerate(zip(gts, poses)): + delta = gt.inverse() * pose + error_t[i] = np.sqrt(delta.trans @ delta.trans) # type: ignore + r_diff = delta.rot.log() + error_r[i] = np.sqrt(r_diff @ r_diff) * 180 / np.pi # type: ignore + + return Error(rot=error_r, trans=error_t) + + +def _check_aligned(traj: Trajectory, gt: Trajectory) -> bool: + """Check if the two trajectories are aligned. + + Args: + traj (Trajectory): One of the trajectories + gt (Trajectory): The other trajectory + + Returns: + bool: True if the two trajectories are aligned, False otherwise + """ + # Check if the two trajectories are aligned + delta = gt.poses[0].inverse() * traj.poses[0] + t = cast(np.ndarray, delta.trans) + r = cast(np.ndarray, delta.rot.log()) + return len(traj.stamps) == len(gt.stamps) and (t @ t < 1e-6) and (r @ r < 1e-6) # type: ignore + + +def ate(traj: Trajectory, gt: Trajectory) -> Error: + """Compute the Absolute Trajectory Error (ATE) between two trajectories. + + Will check if the two trajectories are aligned and if not, will align them. + Will not modify the original trajectories. + + Args: + traj (Trajectory): One of the trajectories + gt (Trajectory): The other trajectory + + Returns: + Error: The computed error + """ + if not _check_aligned(traj, gt): + traj, gt = align(traj, gt) + + # Compute the ATE + return _compute_metric(gt.poses, traj.poses) + + +def rte(traj: Trajectory, gt: Trajectory, window: int = 100) -> Error: + """Compute the Relative Trajectory Error (RTE) between two trajectories. + + Will check if the two trajectories are aligned and if not, will align them. + Will not modify the original trajectories. + + Args: + traj (Trajectory): One of the trajectories + gt (Trajectory): The other trajectory + window (int, optional): Window size for the RTE. Defaults to 100. + + Returns: + Error: The computed error + """ + if not _check_aligned(traj, gt): + traj, gt = align(traj, gt) + + if window <= 0: + raise ValueError("Window size must be positive") + + window_deltas_poses = [] + window_deltas_gts = [] + for i in range(len(gt) - window): + window_deltas_poses.append(traj.poses[i].inverse() * traj.poses[i + window]) + window_deltas_gts.append(gt.poses[i].inverse() * gt.poses[i + window]) + + # Compute the RTE + return _compute_metric(window_deltas_gts, window_deltas_poses) diff --git a/python/evalio/types.py b/python/evalio/types.py index 7040db49..841808c9 100644 --- a/python/evalio/types.py +++ b/python/evalio/types.py @@ -23,8 +23,11 @@ @dataclass(kw_only=True) class Trajectory: stamps: list[Stamp] + """List of timestamps for each pose.""" poses: list[SE3] + """List of poses, in the same order as the timestamps.""" metadata: dict = field(default_factory=dict) + """Metadata associated with the trajectory, such as the dataset name or other information.""" def __post_init__(self): if len(self.stamps) != len(self.poses): @@ -48,12 +51,32 @@ def transform_in_place(self, T: SE3): self.poses[i] = self.poses[i] * T @staticmethod - def load_csv( + def from_csv( path: Path, fieldnames: list[str], delimiter=",", skip_lines: Optional[int] = None, ) -> "Trajectory": + """Flexible loader for stamped poses stored in csv files. + + Will automatically skip any lines that start with a #. Is most useful for loading ground truth data. + + ``` py + from evalio.types import Trajectory + + fieldnames = ["sec", "nsec", "x", "y", "z", "qx", "qy", "qz", "qw"] + trajectory = Trajectory.from_csv(path, fieldnames) + ``` + + Args: + path (Path): Location of file. + fieldnames (list[str]): List of field names to use, in their expected order. See above for an example. + delimiter (str, optional): Delimiter between elements. Defaults to ",". + skip_lines (int, optional): Number of lines to skip, useful for skipping headers. Defaults to 0. + + Returns: + Trajectory: Stored dataset + """ poses = [] stamps = [] @@ -91,13 +114,23 @@ def load_csv( return Trajectory(stamps=stamps, poses=poses) @staticmethod - def load_tum(path: Path) -> "Trajectory": - return Trajectory.load_csv(path, ["sec", "x", "y", "z", "qx", "qy", "qz", "qw"]) + def from_tum(path: Path) -> "Trajectory": + """Load a TUM dataset pose file. Simple wrapper around [from_csv][evalio.types.Trajectory]. + + Args: + path (Path): Location of file. + + Returns: + Trajectory: Stored trajectory + """ + return Trajectory.from_csv(path, ["sec", "x", "y", "z", "qx", "qy", "qz", "qw"]) @staticmethod - def load_experiment(path: Path) -> "Trajectory": + def from_experiment(path: Path) -> "Trajectory": """Load a saved experiment trajectory from file. + Works identically to [from_tum][evalio.types.Trajectory.from_tum], but also loads metadata from the file. + Args: path (Path): Location of trajectory results. @@ -112,7 +145,7 @@ def load_experiment(path: Path) -> "Trajectory": metadata_str = "\n".join(metadata_list) metadata = yaml.safe_load(metadata_str) - trajectory = Trajectory.load_csv( + trajectory = Trajectory.from_csv( path, fieldnames=["sec", "x", "y", "z", "qx", "qy", "qz", "qw"], ) diff --git a/tests/get_lens.py b/tests/get_lens.py new file mode 100644 index 00000000..80ecc2b1 --- /dev/null +++ b/tests/get_lens.py @@ -0,0 +1,23 @@ +from evalio.cli.parser import DatasetBuilder +from rich import print +from evalio.utils import print_warning + +# Helper script to get the lengths of all datasets +# Easier than manually checking each +for d in DatasetBuilder._all_datasets().values(): + lengths = {} + + for seq in d.sequences(): + if not seq.is_downloaded(): + print_warning(f"Sequence {seq.name} not downloaded") + continue + + try: + lengths[seq.name] = len(seq.data_iter()) + except Exception: + print_warning(f"Failed to get length of {seq.name}") + continue + + print(d.dataset_name()) + print(lengths) + print() diff --git a/tests/test_dataset_loading.py b/tests/test_dataset_loading.py index 8449b97c..67444d5f 100644 --- a/tests/test_dataset_loading.py +++ b/tests/test_dataset_loading.py @@ -94,7 +94,7 @@ def test_load_groundtruth(style: StampStyle, tmp_path: Path): with open(gt_file, "w") as f: f.write("\n".join(gt_str)) - gt_returned = Trajectory.load_csv( + gt_returned = Trajectory.from_csv( gt_file, fieldnames=style.attributes() + ["x", "y", "z", "qx", "qy", "qz", "qw"] ) diff --git a/tests/test_cli_stats.py b/tests/test_stats.py similarity index 90% rename from tests/test_cli_stats.py rename to tests/test_stats.py index fe75df7f..c5d86500 100644 --- a/tests/test_cli_stats.py +++ b/tests/test_stats.py @@ -1,7 +1,7 @@ from copy import deepcopy from evalio.types import Stamp from evalio.types import SE3, Trajectory -from evalio.cli.stats import align_stamps, align_poses +from evalio import stats from utils import rand_se3, isclose_se3 import numpy as np @@ -21,7 +21,7 @@ def test_already_aligned(): traj1_out = deepcopy(traj) traj2_out = deepcopy(traj) - align_stamps(traj1_out, traj2_out) + stats.align_stamps(traj1_out, traj2_out) assert (traj1_out, traj2_out) == (traj, traj) @@ -40,7 +40,7 @@ def test_subsample_first(): ) traj1_out, traj2_out = deepcopy(traj1), deepcopy(traj2) - align_stamps(traj1_out, traj2_out) + stats.align_stamps(traj1_out, traj2_out) if traj1_out != traj2: raise ValueError( @@ -68,7 +68,7 @@ def test_overstep(): ) traj1_out, traj2_out = deepcopy(traj1), deepcopy(traj2) - align_stamps(traj1_out, traj2_out) + stats.align_stamps(traj1_out, traj2_out) if traj1_out != traj1: raise ValueError( @@ -81,7 +81,7 @@ def test_overstep(): ) -def test_align_poses(): +def testalign_poses(): np.random.seed(0) gt = Trajectory( metadata={}, @@ -97,7 +97,7 @@ def test_align_poses(): poses=[offset * pose for pose in gt.poses], ) - align_poses(traj2, gt) + stats.align_poses(traj2, gt) for a, b in zip(traj2.poses, gt.poses): assert isclose_se3(a, b), f"{a} != {b}" diff --git a/uv.lock b/uv.lock index 13416dc8..df4c5d62 100644 --- a/uv.lock +++ b/uv.lock @@ -26,11 +26,11 @@ wheels = [ [[package]] name = "argcomplete" -version = "3.6.0" +version = "3.6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/be/29abccb5d9f61a92886a2fba2ac22bf74326b5c4f55d36d0a56094630589/argcomplete-3.6.0.tar.gz", hash = "sha256:2e4e42ec0ba2fff54b0d244d0b1623e86057673e57bafe72dda59c64bd5dee8b", size = 73135 } +sdist = { url = "https://files.pythonhosted.org/packages/16/0f/861e168fc813c56a78b35f3c30d91c6757d1fd185af1110f1aec784b35d0/argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf", size = 73403 } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/94/e786d91ccc3a1fc664c20332825b73da20928eb067cdc984b821948a1acc/argcomplete-3.6.0-py3-none-any.whl", hash = "sha256:4e3e4e10beb20e06444dbac0ac8dda650cb6349caeefe980208d3c548708bedd", size = 43769 }, + { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708 }, ] [[package]] @@ -42,17 +42,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, ] +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "backrefs" +version = "5.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/46/caba1eb32fa5784428ab401a5487f73db4104590ecd939ed9daaf18b47e0/backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd", size = 6773994 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/cb/d019ab87fe70e0fe3946196d50d6a4428623dc0c38a6669c8cae0320fbf3/backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d", size = 380337 }, + { url = "https://files.pythonhosted.org/packages/a9/86/abd17f50ee21b2248075cb6924c6e7f9d23b4925ca64ec660e869c2633f1/backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b", size = 392142 }, + { url = "https://files.pythonhosted.org/packages/b3/04/7b415bd75c8ab3268cc138c76fa648c19495fcc7d155508a0e62f3f82308/backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486", size = 398021 }, + { url = "https://files.pythonhosted.org/packages/04/b8/60dcfb90eb03a06e883a92abbc2ab95c71f0d8c9dd0af76ab1d5ce0b1402/backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585", size = 399915 }, + { url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336 }, +] + [[package]] name = "beautifulsoup4" -version = "4.13.3" +version = "4.13.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f0/3c/adaf39ce1fb4afdd21b611e3d530b183bb7759c9b673d60db0e347fd4439/beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", size = 619516 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 }, + { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285 }, ] [[package]] @@ -66,7 +88,7 @@ wheels = [ [[package]] name = "bump-my-version" -version = "1.1.1" +version = "1.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -79,18 +101,18 @@ dependencies = [ { name = "tomlkit" }, { name = "wcmatch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/0a/b4e0dea19ca92d871b011dfbd4271d2ea627db74a6b651ea642a7c605cf7/bump_my_version-1.1.1.tar.gz", hash = "sha256:2f590e0eabe894196289c296c52170559c09876454514ae8fce5db75bd47289e", size = 1108000 } +sdist = { url = "https://files.pythonhosted.org/packages/13/0a/544e8eb6d46baa99bf16d180b4ddb4509631fa8476e686c8e6c47681afb4/bump_my_version-1.1.2.tar.gz", hash = "sha256:0122845a78502b5a5a635ca17c1efb3e1ec05e77d72d13b2314186b9806882fb", size = 1120309 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/a7/c516c75f624b4dc7afde397af3bf5c01c6b191e18bcccfa44ec0d7d4a4e4/bump_my_version-1.1.1-py3-none-any.whl", hash = "sha256:6bd78e20421f6335c1a49d7e85a2f16ae8966897d0a2dd130a0e8b6b55954686", size = 59474 }, + { url = "https://files.pythonhosted.org/packages/dc/a9/026894e86ce2838d029af1344c71fd57560d1b6e2ce6513c340cbf8e00cb/bump_my_version-1.1.2-py3-none-any.whl", hash = "sha256:71a2a8c3940c87749c4cc404b2ada2fafbeab4e478e0ef54537686905ae58e0d", size = 59495 }, ] [[package]] name = "certifi" -version = "2025.1.31" +version = "2025.4.26" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705 } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 }, ] [[package]] @@ -267,6 +289,11 @@ dev = [ { name = "bump-my-version" }, { name = "cmake" }, { name = "compdb" }, + { name = "mike" }, + { name = "mkdocs" }, + { name = "mkdocs-gen-files" }, + { name = "mkdocs-material" }, + { name = "mkdocstrings", extra = ["python"] }, { name = "nanobind" }, { name = "pytest" }, { name = "ruff" }, @@ -284,17 +311,22 @@ requires-dist = [ { name = "numpy" }, { name = "pyyaml", specifier = ">=6.0" }, { name = "rapidfuzz", specifier = ">=3.12.2" }, - { name = "rerun-sdk", marker = "extra == 'vis'", specifier = ">=0.18.2" }, + { name = "rerun-sdk", marker = "extra == 'vis'", specifier = ">=0.23" }, { name = "rosbags", specifier = ">=0.10" }, { name = "tqdm", specifier = ">=4.66" }, - { name = "typer", specifier = ">=0.15.2" }, + { name = "typer", specifier = ">=0.15.3" }, ] [package.metadata.requires-dev] dev = [ { name = "bump-my-version", specifier = ">=1.1.1" }, - { name = "cmake", specifier = ">=3.30.3" }, + { name = "cmake", specifier = "<4.0.0" }, { name = "compdb", specifier = ">=0.2.0" }, + { name = "mike", specifier = ">=2.1.3" }, + { name = "mkdocs", specifier = ">=1.6.1" }, + { name = "mkdocs-gen-files", specifier = ">=0.5.0" }, + { name = "mkdocs-material", specifier = ">=9.6.11" }, + { name = "mkdocstrings", extras = ["python"], specifier = ">=0.29.1" }, { name = "nanobind", specifier = ">=2.7.0" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "ruff", specifier = ">=0.6.8" }, @@ -329,26 +361,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/70/e07c381e6488a77094f04c85c9caf1c8008cdc30778f7019bc52e5285ef0/gdown-5.2.0-py3-none-any.whl", hash = "sha256:33083832d82b1101bdd0e9df3edd0fbc0e1c5f14c9d8c38d2a35bf1683b526d6", size = 18235 }, ] +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, +] + +[[package]] +name = "griffe" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/3e/5aa9a61f7c3c47b0b52a1d930302992229d191bf4bc76447b324b731510a/griffe-1.7.3.tar.gz", hash = "sha256:52ee893c6a3a968b639ace8015bec9d36594961e156e23315c8e8e51401fa50b", size = 395137 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/c6/5c20af38c2a57c15d87f7f38bee77d63c1d2a3689f74fefaf35915dd12b2/griffe-1.7.3-py3-none-any.whl", hash = "sha256:c6b3ee30c2f0f17f30bcdef5068d6ab7a2a4f1b8bf1a3e74b56fffd21e1c5f75", size = 129303 }, +] + [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, ] [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, ] [[package]] @@ -375,39 +431,87 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656 }, +] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, +] + [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, ] [[package]] name = "lz4" -version = "4.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/bc/b2e79af05be82841706ddd7d78059e5f78e6ca5828f92034394b54e303b7/lz4-4.4.3.tar.gz", hash = "sha256:91ed5b71f9179bf3dbfe85d92b52d4b53de2e559aa4daa3b7de18e0dd24ad77d", size = 171848 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/28/9b72434d3f41f49637138ff4545e3900b34ece8771e20b84d268b28f4d11/lz4-4.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1b98f0a4137d01b84c680813eef6198e1e00f1f28bc20ce7b5c436459a0d146", size = 220711 }, - { url = "https://files.pythonhosted.org/packages/27/08/ab9008c869ad16f158255514e1870156cebf9c2bf0509aadfddeb5dc2183/lz4-4.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20e385cb8bd8321593788f11101d8c89a823a56191978e427e3c5141e129f14b", size = 189494 }, - { url = "https://files.pythonhosted.org/packages/49/3c/00115af6394c26bb54f863eba5680fdb7962747944db0b1df6c757a61054/lz4-4.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c9e32989df06c57f10aa09ad9b30e8a25baf1aefe850e13b0ea5de600477d6a", size = 1265694 }, - { url = "https://files.pythonhosted.org/packages/e1/6d/693b58fe1fcb2118a5bb858417212bcc6b24794ccf3e9ffb4ccaab7ddf1c/lz4-4.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3d2d5df5476b065aae9d1ad551fdc7b17c151b84e8edd9212108946b2337c66", size = 1185404 }, - { url = "https://files.pythonhosted.org/packages/80/c6/05179ce2968c434208f2a816de2ebef86b04249d77c694fdd7c8fba0d12b/lz4-4.4.3-cp311-cp311-win32.whl", hash = "sha256:e365850166729fa82be618f476966161d5c47ea081eafc4febfc542bc85bac5d", size = 88141 }, - { url = "https://files.pythonhosted.org/packages/7c/b3/26e04a07a9f5d3f4682853d0bd4ebf1fc83ceb3c72cc55c50bbfbe15a0a2/lz4-4.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:7f5c05bd4b0909b682608c453acc31f1a9170d55f56d27cd701213e0683fc66a", size = 99826 }, - { url = "https://files.pythonhosted.org/packages/7e/40/9a6db39950ba872c3b75ccf4826288a46b109ded1d20508d6044cc36e33c/lz4-4.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:43461e439ef71d49bb0ee3a1719494cd952a58d205496698e0cde866f22006bc", size = 220484 }, - { url = "https://files.pythonhosted.org/packages/b7/25/edd77ac155e167f0d183f0a30be1665ab581f77108ca6e19d628cd381e42/lz4-4.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ae50a175fb7b900f7aa42575f4fe99c32ca0ff57e5a8c1fd25e1243e67409db", size = 189473 }, - { url = "https://files.pythonhosted.org/packages/55/59/80673123358c0e0b2b773b74ac3d14717e35cfcceac5243b61f88e08b883/lz4-4.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38df5929ffefa9dda120ba1790a2e94fda81916c5aaa1ee652f4b1e515ebb9ed", size = 1264959 }, - { url = "https://files.pythonhosted.org/packages/ea/69/24a3d8609f9a05d93b407d93842d35e953bebf625cb4d128a9105c983d59/lz4-4.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b45914f25d916324531d0259072b402c5f99b67c6e9ac8cbc3d49935aeb1d97", size = 1184842 }, - { url = "https://files.pythonhosted.org/packages/88/6e/680d0fc3dbec31aaffcad23d2e429b2974253ffda4636ea8a7e2cce5461c/lz4-4.4.3-cp312-cp312-win32.whl", hash = "sha256:848c5b040d2cfe35097b1d65d1095d83a3f86374ce879e189533f61405d8763b", size = 88157 }, - { url = "https://files.pythonhosted.org/packages/d4/c9/8fcaf3445d3dc2973861b1a1a27090e23952807facabcf092a587ff77754/lz4-4.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:b1d179bdefd9ddb8d11d7de7825e73fb957511b722a8cb484e417885c210e68c", size = 99833 }, - { url = "https://files.pythonhosted.org/packages/7a/81/61ca14fb0939d03f6ab4710fb92048cde9e1b924ce198912545808ef9e8a/lz4-4.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:174b7ce5456671c73b81bb115defac8a584363d8b38a48ed3ad976e08eea27cd", size = 220487 }, - { url = "https://files.pythonhosted.org/packages/23/9b/8841de45b452b291aa0cae1fb9a961cee4fe119ff8eed1584b1633c5c4e6/lz4-4.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab26b4af13308b8296688b03d74c3b0c8e8ed1f6b2d1454ef97bdb589db409db", size = 189483 }, - { url = "https://files.pythonhosted.org/packages/d9/18/379429ec69468ee57e1641dc4e1aa324a39510f2ab4d9991a036fc3e74ad/lz4-4.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61e08d84e3bf8ca9f43dc6b33f8cd7ba19f49864e2c91eb2160f83b6f9a268fa", size = 1264934 }, - { url = "https://files.pythonhosted.org/packages/c3/fa/3578da2d0f8062ae53bcc5ef2e9a225896b05332fff746ebe2fd5889eee7/lz4-4.4.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71ebdaadf546d6d393b9a21796172723724b737e84f68f36caf367d1c87a86a1", size = 1184767 }, - { url = "https://files.pythonhosted.org/packages/a1/ed/af96817ac69772d3d676a86f59a583740d25b2f45163625cb3632479102f/lz4-4.4.3-cp313-cp313-win32.whl", hash = "sha256:1f25e1b571a8be2c3d60d46679ef2471ae565f7ba9ba8382596695413523b188", size = 88164 }, - { url = "https://files.pythonhosted.org/packages/96/1f/a6b4b87038d1057675afdd017ca606662f266a41018ed617bc3395a5d10d/lz4-4.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:da091dd8c96dbda124d766231f38619afd5c544051fb4424d2566c905957d342", size = 99840 }, +version = "4.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/5a/945f5086326d569f14c84ac6f7fcc3229f0b9b1e8cc536b951fd53dfb9e1/lz4-4.4.4.tar.gz", hash = "sha256:070fd0627ec4393011251a094e08ed9fdcc78cb4e7ab28f507638eee4e39abda", size = 171884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/e8/63843dc5ecb1529eb38e1761ceed04a0ad52a9ad8929ab8b7930ea2e4976/lz4-4.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ddfc7194cd206496c445e9e5b0c47f970ce982c725c87bd22de028884125b68f", size = 220898 }, + { url = "https://files.pythonhosted.org/packages/e4/94/c53de5f07c7dc11cf459aab2a1d754f5df5f693bfacbbe1e4914bfd02f1e/lz4-4.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:714f9298c86f8e7278f1c6af23e509044782fa8220eb0260f8f8f1632f820550", size = 189685 }, + { url = "https://files.pythonhosted.org/packages/fe/59/c22d516dd0352f2a3415d1f665ccef2f3e74ecec3ca6a8f061a38f97d50d/lz4-4.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8474c91de47733856c6686df3c4aca33753741da7e757979369c2c0d32918ba", size = 1239225 }, + { url = "https://files.pythonhosted.org/packages/81/af/665685072e71f3f0e626221b7922867ec249cd8376aca761078c8f11f5da/lz4-4.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80dd27d7d680ea02c261c226acf1d41de2fd77af4fb2da62b278a9376e380de0", size = 1265881 }, + { url = "https://files.pythonhosted.org/packages/90/04/b4557ae381d3aa451388a29755cc410066f5e2f78c847f66f154f4520a68/lz4-4.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b7d6dddfd01b49aedb940fdcaf32f41dc58c926ba35f4e31866aeec2f32f4f4", size = 1185593 }, + { url = "https://files.pythonhosted.org/packages/7b/e4/03636979f4e8bf92c557f998ca98ee4e6ef92e92eaf0ed6d3c7f2524e790/lz4-4.4.4-cp311-cp311-win32.whl", hash = "sha256:4134b9fd70ac41954c080b772816bb1afe0c8354ee993015a83430031d686a4c", size = 88259 }, + { url = "https://files.pythonhosted.org/packages/07/f0/9efe53b4945441a5d2790d455134843ad86739855b7e6199977bf6dc8898/lz4-4.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:f5024d3ca2383470f7c4ef4d0ed8eabad0b22b23eeefde1c192cf1a38d5e9f78", size = 99916 }, + { url = "https://files.pythonhosted.org/packages/87/c8/1675527549ee174b9e1db089f7ddfbb962a97314657269b1e0344a5eaf56/lz4-4.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:6ea715bb3357ea1665f77874cf8f55385ff112553db06f3742d3cdcec08633f7", size = 89741 }, + { url = "https://files.pythonhosted.org/packages/f7/2d/5523b4fabe11cd98f040f715728d1932eb7e696bfe94391872a823332b94/lz4-4.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:23ae267494fdd80f0d2a131beff890cf857f1b812ee72dbb96c3204aab725553", size = 220669 }, + { url = "https://files.pythonhosted.org/packages/91/06/1a5bbcacbfb48d8ee5b6eb3fca6aa84143a81d92946bdb5cd6b005f1863e/lz4-4.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fff9f3a1ed63d45cb6514bfb8293005dc4141341ce3500abdfeb76124c0b9b2e", size = 189661 }, + { url = "https://files.pythonhosted.org/packages/fa/08/39eb7ac907f73e11a69a11576a75a9e36406b3241c0ba41453a7eb842abb/lz4-4.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ea7f07329f85a8eda4d8cf937b87f27f0ac392c6400f18bea2c667c8b7f8ecc", size = 1238775 }, + { url = "https://files.pythonhosted.org/packages/e9/26/05840fbd4233e8d23e88411a066ab19f1e9de332edddb8df2b6a95c7fddc/lz4-4.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ccab8f7f7b82f9fa9fc3b0ba584d353bd5aa818d5821d77d5b9447faad2aaad", size = 1265143 }, + { url = "https://files.pythonhosted.org/packages/b7/5d/5f2db18c298a419932f3ab2023deb689863cf8fd7ed875b1c43492479af2/lz4-4.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43e9d48b2daf80e486213128b0763deed35bbb7a59b66d1681e205e1702d735", size = 1185032 }, + { url = "https://files.pythonhosted.org/packages/c4/e6/736ab5f128694b0f6aac58343bcf37163437ac95997276cd0be3ea4c3342/lz4-4.4.4-cp312-cp312-win32.whl", hash = "sha256:33e01e18e4561b0381b2c33d58e77ceee850a5067f0ece945064cbaac2176962", size = 88284 }, + { url = "https://files.pythonhosted.org/packages/40/b8/243430cb62319175070e06e3a94c4c7bd186a812e474e22148ae1290d47d/lz4-4.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d21d1a2892a2dcc193163dd13eaadabb2c1b803807a5117d8f8588b22eaf9f12", size = 99918 }, + { url = "https://files.pythonhosted.org/packages/6c/e1/0686c91738f3e6c2e1a243e0fdd4371667c4d2e5009b0a3605806c2aa020/lz4-4.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:2f4f2965c98ab254feddf6b5072854a6935adab7bc81412ec4fe238f07b85f62", size = 89736 }, + { url = "https://files.pythonhosted.org/packages/3b/3c/d1d1b926d3688263893461e7c47ed7382a969a0976fc121fc678ec325fc6/lz4-4.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed6eb9f8deaf25ee4f6fad9625d0955183fdc90c52b6f79a76b7f209af1b6e54", size = 220678 }, + { url = "https://files.pythonhosted.org/packages/26/89/8783d98deb058800dabe07e6cdc90f5a2a8502a9bad8c5343c641120ace2/lz4-4.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:18ae4fe3bafb344dbd09f976d45cbf49c05c34416f2462828f9572c1fa6d5af7", size = 189670 }, + { url = "https://files.pythonhosted.org/packages/22/ab/a491ace69a83a8914a49f7391e92ca0698f11b28d5ce7b2ececa2be28e9a/lz4-4.4.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57fd20c5fc1a49d1bbd170836fccf9a338847e73664f8e313dce6ac91b8c1e02", size = 1238746 }, + { url = "https://files.pythonhosted.org/packages/97/12/a1f2f4fdc6b7159c0d12249456f9fe454665b6126e98dbee9f2bd3cf735c/lz4-4.4.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9cb387c33f014dae4db8cb4ba789c8d2a0a6d045ddff6be13f6c8d9def1d2a6", size = 1265119 }, + { url = "https://files.pythonhosted.org/packages/50/6e/e22e50f5207649db6ea83cd31b79049118305be67e96bec60becf317afc6/lz4-4.4.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0be9f68240231e1e44118a4ebfecd8a5d4184f0bdf5c591c98dd6ade9720afd", size = 1184954 }, + { url = "https://files.pythonhosted.org/packages/4c/c4/2a458039645fcc6324ece731d4d1361c5daf960b553d1fcb4261ba07d51c/lz4-4.4.4-cp313-cp313-win32.whl", hash = "sha256:e9ec5d45ea43684f87c316542af061ef5febc6a6b322928f059ce1fb289c298a", size = 88289 }, + { url = "https://files.pythonhosted.org/packages/00/96/b8e24ea7537ab418074c226279acfcaa470e1ea8271003e24909b6db942b/lz4-4.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:a760a175b46325b2bb33b1f2bbfb8aa21b48e1b9653e29c10b6834f9bb44ead4", size = 99925 }, + { url = "https://files.pythonhosted.org/packages/a5/a5/f9838fe6aa132cfd22733ed2729d0592259fff074cefb80f19aa0607367b/lz4-4.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:f4c21648d81e0dda38b4720dccc9006ae33b0e9e7ffe88af6bf7d4ec124e2fba", size = 89743 }, +] + +[[package]] +name = "markdown" +version = "3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/15/222b423b0b88689c266d9eac4e61396fe2cc53464459d6a37618ac863b24/markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f", size = 360906 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/3f/afe76f8e2246ffbc867440cbcf90525264df0e658f8a5ca1f872b3f6192a/markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc", size = 106210 }, ] [[package]] @@ -422,6 +526,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, ] +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -431,6 +583,165 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 }, +] + +[[package]] +name = "mike" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "importlib-resources" }, + { name = "jinja2" }, + { name = "mkdocs" }, + { name = "pyparsing" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "verspec" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/f7/2933f1a1fb0e0f077d5d6a92c6c7f8a54e6128241f116dff4df8b6050bbf/mike-2.1.3.tar.gz", hash = "sha256:abd79b8ea483fb0275b7972825d3082e5ae67a41820f8d8a0dc7a3f49944e810", size = 38119 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl", hash = "sha256:d90c64077e84f06272437b464735130d380703a76a5738b152932884c60c062a", size = 33754 }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/44/140469d87379c02f1e1870315f3143718036a983dd0416650827b8883192/mkdocs_autorefs-1.4.1.tar.gz", hash = "sha256:4b5b6235a4becb2b10425c2fa191737e415b37aa3418919db33e5d774c9db079", size = 4131355 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/29/1125f7b11db63e8e32bcfa0752a4eea30abff3ebd0796f808e14571ddaa2/mkdocs_autorefs-1.4.1-py3-none-any.whl", hash = "sha256:9793c5ac06a6ebbe52ec0f8439256e66187badf4b5334b5fde0b128ec134df4f", size = 5782047 }, +] + +[[package]] +name = "mkdocs-gen-files" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/85/2d634462fd59136197d3126ca431ffb666f412e3db38fd5ce3a60566303e/mkdocs_gen_files-0.5.0.tar.gz", hash = "sha256:4c7cf256b5d67062a788f6b1d035e157fc1a9498c2399be9af5257d4ff4d19bc", size = 7539 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/0f/1e55b3fd490ad2cecb6e7b31892d27cb9fc4218ec1dab780440ba8579e74/mkdocs_gen_files-0.5.0-py3-none-any.whl", hash = "sha256:7ac060096f3f40bd19039e7277dd3050be9a453c8ac578645844d4d91d7978ea", size = 8380 }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 }, +] + +[[package]] +name = "mkdocs-material" +version = "9.6.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/ef/25fc10dbbb8faeeeb10ed7734d84a347cd2ec5d7200733f11c5553c02608/mkdocs_material-9.6.12.tar.gz", hash = "sha256:add6a6337b29f9ea7912cb1efc661de2c369060b040eb5119855d794ea85b473", size = 3951532 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/00/592940f4d150327a4f455171b2c9d4c3be7779a88e18b0a086183fcd8f06/mkdocs_material-9.6.12-py3-none-any.whl", hash = "sha256:92b4fbdc329e4febc267ca6e2c51e8501fa97b2225c5f4deb4d4e43550f8e61e", size = 8703654 }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 }, +] + +[[package]] +name = "mkdocstrings" +version = "0.29.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/e8/d22922664a627a0d3d7ff4a6ca95800f5dde54f411982591b4621a76225d/mkdocstrings-0.29.1.tar.gz", hash = "sha256:8722f8f8c5cd75da56671e0a0c1bbed1df9946c0cef74794d6141b34011abd42", size = 1212686 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/14/22533a578bf8b187e05d67e2c1721ce10e3f526610eebaf7a149d557ea7a/mkdocstrings-0.29.1-py3-none-any.whl", hash = "sha256:37a9736134934eea89cbd055a513d40a020d87dfcae9e3052c2a6b8cd4af09b6", size = 1631075 }, +] + +[package.optional-dependencies] +python = [ + { name = "mkdocstrings-python" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "1.16.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffe" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/c8/600c4201b6b9e72bab16802316d0c90ce04089f8e6bb5e064cd2a5abba7e/mkdocstrings_python-1.16.10.tar.gz", hash = "sha256:f9eedfd98effb612ab4d0ed6dd2b73aff6eba5215e0a65cea6d877717f75502e", size = 205771 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/37/19549c5e0179785308cc988a68e16aa7550e4e270ec8a9878334e86070c6/mkdocstrings_python-1.16.10-py3-none-any.whl", hash = "sha256:63bb9f01f8848a644bdb6289e86dc38ceddeaa63ecc2e291e3b2ca52702a6643", size = 124112 }, +] + [[package]] name = "nanobind" version = "2.7.0" @@ -442,120 +753,157 @@ wheels = [ [[package]] name = "numpy" -version = "2.2.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/fb/09e778ee3a8ea0d4dc8329cca0a9c9e65fed847d08e37eba74cb7ed4b252/numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4", size = 21254989 }, - { url = "https://files.pythonhosted.org/packages/a2/0a/1212befdbecab5d80eca3cde47d304cad986ad4eec7d85a42e0b6d2cc2ef/numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4", size = 14425910 }, - { url = "https://files.pythonhosted.org/packages/2b/3e/e7247c1d4f15086bb106c8d43c925b0b2ea20270224f5186fa48d4fb5cbd/numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f", size = 5426490 }, - { url = "https://files.pythonhosted.org/packages/5d/fa/aa7cd6be51419b894c5787a8a93c3302a1ed4f82d35beb0613ec15bdd0e2/numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880", size = 6967754 }, - { url = "https://files.pythonhosted.org/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079 }, - { url = "https://files.pythonhosted.org/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819 }, - { url = "https://files.pythonhosted.org/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470 }, - { url = "https://files.pythonhosted.org/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144 }, - { url = "https://files.pythonhosted.org/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368 }, - { url = "https://files.pythonhosted.org/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526 }, - { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156 }, - { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092 }, - { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515 }, - { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558 }, - { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742 }, - { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051 }, - { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972 }, - { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106 }, - { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190 }, - { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305 }, - { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 }, - { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 }, - { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 }, - { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092 }, - { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 }, - { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 }, - { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 }, - { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 }, - { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 }, - { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 }, - { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829 }, - { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419 }, - { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414 }, - { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379 }, - { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 }, - { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 }, - { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 }, - { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 }, - { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 }, - { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, +version = "2.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/fb/e4e4c254ba40e8f0c78218f9e86304628c75b6900509b601c8433bdb5da7/numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b", size = 21256475 }, + { url = "https://files.pythonhosted.org/packages/81/32/dd1f7084f5c10b2caad778258fdaeedd7fbd8afcd2510672811e6138dfac/numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda", size = 14461474 }, + { url = "https://files.pythonhosted.org/packages/0e/65/937cdf238ef6ac54ff749c0f66d9ee2b03646034c205cea9b6c51f2f3ad1/numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d", size = 5426875 }, + { url = "https://files.pythonhosted.org/packages/25/17/814515fdd545b07306eaee552b65c765035ea302d17de1b9cb50852d2452/numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54", size = 6969176 }, + { url = "https://files.pythonhosted.org/packages/e5/32/a66db7a5c8b5301ec329ab36d0ecca23f5e18907f43dbd593c8ec326d57c/numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610", size = 14374850 }, + { url = "https://files.pythonhosted.org/packages/ad/c9/1bf6ada582eebcbe8978f5feb26584cd2b39f94ededeea034ca8f84af8c8/numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b", size = 16430306 }, + { url = "https://files.pythonhosted.org/packages/6a/f0/3f741863f29e128f4fcfdb99253cc971406b402b4584663710ee07f5f7eb/numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be", size = 15884767 }, + { url = "https://files.pythonhosted.org/packages/98/d9/4ccd8fd6410f7bf2d312cbc98892e0e43c2fcdd1deae293aeb0a93b18071/numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906", size = 18219515 }, + { url = "https://files.pythonhosted.org/packages/b1/56/783237243d4395c6dd741cf16eeb1a9035ee3d4310900e6b17e875d1b201/numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175", size = 6607842 }, + { url = "https://files.pythonhosted.org/packages/98/89/0c93baaf0094bdaaaa0536fe61a27b1dce8a505fa262a865ec142208cfe9/numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd", size = 12949071 }, + { url = "https://files.pythonhosted.org/packages/e2/f7/1fd4ff108cd9d7ef929b8882692e23665dc9c23feecafbb9c6b80f4ec583/numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051", size = 20948633 }, + { url = "https://files.pythonhosted.org/packages/12/03/d443c278348371b20d830af155ff2079acad6a9e60279fac2b41dbbb73d8/numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc", size = 14176123 }, + { url = "https://files.pythonhosted.org/packages/2b/0b/5ca264641d0e7b14393313304da48b225d15d471250376f3fbdb1a2be603/numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e", size = 5163817 }, + { url = "https://files.pythonhosted.org/packages/04/b3/d522672b9e3d28e26e1613de7675b441bbd1eaca75db95680635dd158c67/numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa", size = 6698066 }, + { url = "https://files.pythonhosted.org/packages/a0/93/0f7a75c1ff02d4b76df35079676b3b2719fcdfb39abdf44c8b33f43ef37d/numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571", size = 14087277 }, + { url = "https://files.pythonhosted.org/packages/b0/d9/7c338b923c53d431bc837b5b787052fef9ae68a56fe91e325aac0d48226e/numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073", size = 16135742 }, + { url = "https://files.pythonhosted.org/packages/2d/10/4dec9184a5d74ba9867c6f7d1e9f2e0fb5fe96ff2bf50bb6f342d64f2003/numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8", size = 15581825 }, + { url = "https://files.pythonhosted.org/packages/80/1f/2b6fcd636e848053f5b57712a7d1880b1565eec35a637fdfd0a30d5e738d/numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae", size = 17899600 }, + { url = "https://files.pythonhosted.org/packages/ec/87/36801f4dc2623d76a0a3835975524a84bd2b18fe0f8835d45c8eae2f9ff2/numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb", size = 6312626 }, + { url = "https://files.pythonhosted.org/packages/8b/09/4ffb4d6cfe7ca6707336187951992bd8a8b9142cf345d87ab858d2d7636a/numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282", size = 12645715 }, + { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102 }, + { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709 }, + { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173 }, + { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502 }, + { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417 }, + { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611 }, + { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747 }, + { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594 }, + { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356 }, + { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778 }, + { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279 }, + { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247 }, + { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087 }, + { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964 }, + { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214 }, + { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788 }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672 }, + { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102 }, + { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096 }, ] [[package]] name = "optype" -version = "0.9.2" +version = "0.9.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/c2/76a520809f1a5dd970eab9cc7da3aaa0e16e3fb8a0252fecec1a7a23ad73/optype-0.9.2.tar.gz", hash = "sha256:d7487a5b7fe96bacfc5dc8fab6b90c14f1e65e7ba6e7b22a8bef48d29690edac", size = 96126 } +sdist = { url = "https://files.pythonhosted.org/packages/88/3c/9d59b0167458b839273ad0c4fc5f62f787058d8f5aed7f71294963a99471/optype-0.9.3.tar.gz", hash = "sha256:5f09d74127d316053b26971ce441a4df01f3a01943601d3712dd6f34cdfbaf48", size = 96143 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/1a/40593f6fb9432fea750c9833e0cff0cd71452b9711b9df83faa251b0fe4e/optype-0.9.2-py3-none-any.whl", hash = "sha256:f9aee29e24794a7af637af80347b9fefbb110dffe012c133079615e551e52ef9", size = 84314 }, + { url = "https://files.pythonhosted.org/packages/73/d8/ac50e2982bdc2d3595dc2bfe3c7e5a0574b5e407ad82d70b5f3707009671/optype-0.9.3-py3-none-any.whl", hash = "sha256:2935c033265938d66cc4198b0aca865572e635094e60e6e79522852f029d9e8d", size = 84357 }, ] [[package]] name = "packaging" -version = "24.2" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, +] + +[[package]] +name = "paginate" +version = "0.5.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, ] [[package]] name = "pillow" -version = "11.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/d6/2000bfd8d5414fb70cbbe52c8332f2283ff30ed66a9cde42716c8ecbe22c/pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457", size = 3229968 }, - { url = "https://files.pythonhosted.org/packages/d9/45/3fe487010dd9ce0a06adf9b8ff4f273cc0a44536e234b0fad3532a42c15b/pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35", size = 3101806 }, - { url = "https://files.pythonhosted.org/packages/e3/72/776b3629c47d9d5f1c160113158a7a7ad177688d3a1159cd3b62ded5a33a/pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2", size = 4322283 }, - { url = "https://files.pythonhosted.org/packages/e4/c2/e25199e7e4e71d64eeb869f5b72c7ddec70e0a87926398785ab944d92375/pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070", size = 4402945 }, - { url = "https://files.pythonhosted.org/packages/c1/ed/51d6136c9d5911f78632b1b86c45241c712c5a80ed7fa7f9120a5dff1eba/pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6", size = 4361228 }, - { url = "https://files.pythonhosted.org/packages/48/a4/fbfe9d5581d7b111b28f1d8c2762dee92e9821bb209af9fa83c940e507a0/pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1", size = 4484021 }, - { url = "https://files.pythonhosted.org/packages/39/db/0b3c1a5018117f3c1d4df671fb8e47d08937f27519e8614bbe86153b65a5/pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2", size = 4287449 }, - { url = "https://files.pythonhosted.org/packages/d9/58/bc128da7fea8c89fc85e09f773c4901e95b5936000e6f303222490c052f3/pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96", size = 4419972 }, - { url = "https://files.pythonhosted.org/packages/5f/bb/58f34379bde9fe197f51841c5bbe8830c28bbb6d3801f16a83b8f2ad37df/pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f", size = 2291201 }, - { url = "https://files.pythonhosted.org/packages/3a/c6/fce9255272bcf0c39e15abd2f8fd8429a954cf344469eaceb9d0d1366913/pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761", size = 2625686 }, - { url = "https://files.pythonhosted.org/packages/c8/52/8ba066d569d932365509054859f74f2a9abee273edcef5cd75e4bc3e831e/pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71", size = 2375194 }, - { url = "https://files.pythonhosted.org/packages/95/20/9ce6ed62c91c073fcaa23d216e68289e19d95fb8188b9fb7a63d36771db8/pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a", size = 3226818 }, - { url = "https://files.pythonhosted.org/packages/b9/d8/f6004d98579a2596c098d1e30d10b248798cceff82d2b77aa914875bfea1/pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b", size = 3101662 }, - { url = "https://files.pythonhosted.org/packages/08/d9/892e705f90051c7a2574d9f24579c9e100c828700d78a63239676f960b74/pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3", size = 4329317 }, - { url = "https://files.pythonhosted.org/packages/8c/aa/7f29711f26680eab0bcd3ecdd6d23ed6bce180d82e3f6380fb7ae35fcf3b/pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a", size = 4412999 }, - { url = "https://files.pythonhosted.org/packages/c8/c4/8f0fe3b9e0f7196f6d0bbb151f9fba323d72a41da068610c4c960b16632a/pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1", size = 4368819 }, - { url = "https://files.pythonhosted.org/packages/38/0d/84200ed6a871ce386ddc82904bfadc0c6b28b0c0ec78176871a4679e40b3/pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f", size = 4496081 }, - { url = "https://files.pythonhosted.org/packages/84/9c/9bcd66f714d7e25b64118e3952d52841a4babc6d97b6d28e2261c52045d4/pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91", size = 4296513 }, - { url = "https://files.pythonhosted.org/packages/db/61/ada2a226e22da011b45f7104c95ebda1b63dcbb0c378ad0f7c2a710f8fd2/pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c", size = 4431298 }, - { url = "https://files.pythonhosted.org/packages/e7/c4/fc6e86750523f367923522014b821c11ebc5ad402e659d8c9d09b3c9d70c/pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6", size = 2291630 }, - { url = "https://files.pythonhosted.org/packages/08/5c/2104299949b9d504baf3f4d35f73dbd14ef31bbd1ddc2c1b66a5b7dfda44/pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf", size = 2626369 }, - { url = "https://files.pythonhosted.org/packages/37/f3/9b18362206b244167c958984b57c7f70a0289bfb59a530dd8af5f699b910/pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5", size = 2375240 }, - { url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640 }, - { url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437 }, - { url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605 }, - { url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173 }, - { url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145 }, - { url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340 }, - { url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906 }, - { url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759 }, - { url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657 }, - { url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304 }, - { url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117 }, - { url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060 }, - { url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192 }, - { url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805 }, - { url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623 }, - { url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191 }, - { url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 }, - { url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 }, - { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 }, +version = "11.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450 }, + { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550 }, + { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018 }, + { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006 }, + { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773 }, + { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069 }, + { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460 }, + { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304 }, + { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809 }, + { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338 }, + { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918 }, + { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185 }, + { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306 }, + { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121 }, + { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707 }, + { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921 }, + { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523 }, + { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836 }, + { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390 }, + { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309 }, + { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768 }, + { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087 }, + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098 }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166 }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674 }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005 }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707 }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008 }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420 }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655 }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329 }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388 }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950 }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759 }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284 }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826 }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329 }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049 }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408 }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863 }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938 }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774 }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895 }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234 }, + { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734 }, + { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841 }, + { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470 }, + { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013 }, + { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165 }, + { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586 }, + { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, ] [[package]] @@ -569,14 +917,14 @@ wheels = [ [[package]] name = "prompt-toolkit" -version = "3.0.50" +version = "3.0.51" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 }, ] [[package]] @@ -625,82 +973,96 @@ wheels = [ [[package]] name = "pydantic" -version = "2.10.6" +version = "2.11.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } +sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, + { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 }, ] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, - { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, - { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, - { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, - { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, - { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, - { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, - { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, - { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, - { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, - { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, - { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, - { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, - { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, - { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, - { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, - { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, - { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, - { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, - { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, - { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, - { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, - { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, - { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, - { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, - { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, - { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, - { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, - { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, - { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, - { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, - { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, - { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, - { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, - { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, - { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, - { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, - { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, - { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, - { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, - { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, - { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, +sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/7f/c6298830cb780c46b4f46bb24298d01019ffa4d21769f39b908cd14bbd50/pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24", size = 2044224 }, + { url = "https://files.pythonhosted.org/packages/a8/65/6ab3a536776cad5343f625245bd38165d6663256ad43f3a200e5936afd6c/pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30", size = 1858845 }, + { url = "https://files.pythonhosted.org/packages/e9/15/9a22fd26ba5ee8c669d4b8c9c244238e940cd5d818649603ca81d1c69861/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595", size = 1910029 }, + { url = "https://files.pythonhosted.org/packages/d5/33/8cb1a62818974045086f55f604044bf35b9342900318f9a2a029a1bec460/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e", size = 1997784 }, + { url = "https://files.pythonhosted.org/packages/c0/ca/49958e4df7715c71773e1ea5be1c74544923d10319173264e6db122543f9/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a", size = 2141075 }, + { url = "https://files.pythonhosted.org/packages/7b/a6/0b3a167a9773c79ba834b959b4e18c3ae9216b8319bd8422792abc8a41b1/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505", size = 2745849 }, + { url = "https://files.pythonhosted.org/packages/0b/60/516484135173aa9e5861d7a0663dce82e4746d2e7f803627d8c25dfa5578/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f", size = 2005794 }, + { url = "https://files.pythonhosted.org/packages/86/70/05b1eb77459ad47de00cf78ee003016da0cedf8b9170260488d7c21e9181/pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77", size = 2123237 }, + { url = "https://files.pythonhosted.org/packages/c7/57/12667a1409c04ae7dc95d3b43158948eb0368e9c790be8b095cb60611459/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961", size = 2086351 }, + { url = "https://files.pythonhosted.org/packages/57/61/cc6d1d1c1664b58fdd6ecc64c84366c34ec9b606aeb66cafab6f4088974c/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1", size = 2258914 }, + { url = "https://files.pythonhosted.org/packages/d1/0a/edb137176a1f5419b2ddee8bde6a0a548cfa3c74f657f63e56232df8de88/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c", size = 2257385 }, + { url = "https://files.pythonhosted.org/packages/26/3c/48ca982d50e4b0e1d9954919c887bdc1c2b462801bf408613ccc641b3daa/pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896", size = 1923765 }, + { url = "https://files.pythonhosted.org/packages/33/cd/7ab70b99e5e21559f5de38a0928ea84e6f23fdef2b0d16a6feaf942b003c/pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83", size = 1950688 }, + { url = "https://files.pythonhosted.org/packages/4b/ae/db1fc237b82e2cacd379f63e3335748ab88b5adde98bf7544a1b1bd10a84/pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89", size = 1908185 }, + { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 }, + { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 }, + { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 }, + { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 }, + { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 }, + { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 }, + { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 }, + { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 }, + { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 }, + { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 }, + { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 }, + { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 }, + { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 }, + { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 }, + { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 }, + { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 }, + { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 }, + { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 }, + { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 }, + { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 }, + { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 }, + { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 }, + { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 }, + { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 }, + { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 }, + { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 }, + { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 }, + { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 }, + { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 }, + { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 }, + { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 }, + { url = "https://files.pythonhosted.org/packages/0b/76/1794e440c1801ed35415238d2c728f26cd12695df9057154ad768b7b991c/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a", size = 2042858 }, + { url = "https://files.pythonhosted.org/packages/73/b4/9cd7b081fb0b1b4f8150507cd59d27b275c3e22ad60b35cb19ea0977d9b9/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc", size = 1873745 }, + { url = "https://files.pythonhosted.org/packages/e1/d7/9ddb7575d4321e40d0363903c2576c8c0c3280ebea137777e5ab58d723e3/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b", size = 1904188 }, + { url = "https://files.pythonhosted.org/packages/d1/a8/3194ccfe461bb08da19377ebec8cb4f13c9bd82e13baebc53c5c7c39a029/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe", size = 2083479 }, + { url = "https://files.pythonhosted.org/packages/42/c7/84cb569555d7179ca0b3f838cef08f66f7089b54432f5b8599aac6e9533e/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5", size = 2118415 }, + { url = "https://files.pythonhosted.org/packages/3b/67/72abb8c73e0837716afbb58a59cc9e3ae43d1aa8677f3b4bc72c16142716/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761", size = 2079623 }, + { url = "https://files.pythonhosted.org/packages/0b/cd/c59707e35a47ba4cbbf153c3f7c56420c58653b5801b055dc52cccc8e2dc/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850", size = 2250175 }, + { url = "https://files.pythonhosted.org/packages/84/32/e4325a6676b0bed32d5b084566ec86ed7fd1e9bcbfc49c578b1755bde920/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544", size = 2254674 }, + { url = "https://files.pythonhosted.org/packages/12/6f/5596dc418f2e292ffc661d21931ab34591952e2843e7168ea5a52591f6ff/pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5", size = 2080951 }, ] [[package]] name = "pydantic-settings" -version = "2.8.1" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 } +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 }, + { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356 }, ] [[package]] @@ -712,6 +1074,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, ] +[[package]] +name = "pymdown-extensions" +version = "10.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/44/e6de2fdc880ad0ec7547ca2e087212be815efbc9a425a8d5ba9ede602cbb/pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b", size = 846846 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/f5/b9e2a42aa8f9e34d52d66de87941ecd236570c7ed2e87775ed23bbe4e224/pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9", size = 264467 }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 }, +] + [[package]] name = "pysocks" version = "1.7.1" @@ -736,13 +1120,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + [[package]] name = "python-dotenv" -version = "1.0.1" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, ] [[package]] @@ -780,6 +1176,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911 }, +] + [[package]] name = "questionary" version = "2.1.0" @@ -794,61 +1202,61 @@ wheels = [ [[package]] name = "rapidfuzz" -version = "3.12.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/be/8dff25a6157dfbde9867720b1282157fe7b809e085130bb89d7655c62186/rapidfuzz-3.12.2.tar.gz", hash = "sha256:b0ba1ccc22fff782e7152a3d3d0caca44ec4e32dc48ba01c560b8593965b5aa3", size = 57907839 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/41/985b8786f7895f7a7f03f80b547e04a5b9f41187f43de386ad2f32b9f9fc/rapidfuzz-3.12.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9c4d984621ae17404c58f8d06ed8b025e167e52c0e6a511dfec83c37e9220cd", size = 1960568 }, - { url = "https://files.pythonhosted.org/packages/90/9e/9278b4160bf86346fc5f110b5daf07af629343bfcd04a9366d355bc6104e/rapidfuzz-3.12.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f9132c55d330f0a1d34ce6730a76805323a6250d97468a1ca766a883d6a9a25", size = 1434362 }, - { url = "https://files.pythonhosted.org/packages/e7/53/fe3fb50111e203da4e82b8694c29cbf44101cdbf1efd7ef721cdf85e0aca/rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b343b6cb4b2c3dbc8d2d4c5ee915b6088e3b144ddf8305a57eaab16cf9fc74", size = 1417839 }, - { url = "https://files.pythonhosted.org/packages/fd/c4/aa11749bc9d9c0539061d32f2c525d99e11588867d3d6e94693ccd4e0dd0/rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24081077b571ec4ee6d5d7ea0e49bc6830bf05b50c1005028523b9cd356209f3", size = 5620525 }, - { url = "https://files.pythonhosted.org/packages/5f/62/463c618a5a8a44bf6b087325353e13dbd5bc19c44cc06134d3c9eff0d04a/rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c988a4fc91856260355773bf9d32bebab2083d4c6df33fafeddf4330e5ae9139", size = 1671267 }, - { url = "https://files.pythonhosted.org/packages/ca/b6/ec87c56ed0fab59f8220f5b832d5c1dd374667bee73318a01392ccc8c23d/rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:780b4469ee21cf62b1b2e8ada042941fd2525e45d5fb6a6901a9798a0e41153c", size = 1683415 }, - { url = "https://files.pythonhosted.org/packages/46/08/862e65a1022cbfa2935e7b3f04cdaa73b0967ebf4762ddf509735da47d73/rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edd84b0a323885493c893bad16098c5e3b3005d7caa995ae653da07373665d97", size = 3139234 }, - { url = "https://files.pythonhosted.org/packages/ee/fa/7e8c0d1d26a4b892344c743f17e2c8482f749b616cd651590bd60994b49f/rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efa22059c765b3d8778083805b199deaaf643db070f65426f87d274565ddf36a", size = 2523730 }, - { url = "https://files.pythonhosted.org/packages/8a/52/1d5b80e990c2e9998e47be118c2dbabda75daa2a5f5ff978df1ed76d7f81/rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:095776b11bb45daf7c2973dd61cc472d7ea7f2eecfa454aef940b4675659b92f", size = 7880525 }, - { url = "https://files.pythonhosted.org/packages/0c/18/9c8cd7378272590a1eb0855b587f3a1fbd3492bd1612825d675320eeeb1b/rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7e2574cf4aa86065600b664a1ac7b8b8499107d102ecde836aaaa403fc4f1784", size = 2905180 }, - { url = "https://files.pythonhosted.org/packages/4b/94/992de5d0fc9269a93ce62979aced028e0939d3477ea99d87fd0e22f44e8d/rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d5a3425a6c50fd8fbd991d8f085ddb504791dae6ef9cc3ab299fea2cb5374bef", size = 3548613 }, - { url = "https://files.pythonhosted.org/packages/9b/25/ed3a0317f118131ee297de5936e1587e48b059e6438f4bbf92ef3bbc4927/rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97fb05e1ddb7b71a054040af588b0634214ee87cea87900d309fafc16fd272a4", size = 4583047 }, - { url = "https://files.pythonhosted.org/packages/4d/27/10585a5a62ff6ebbefa3e836a3fd8c123e2ed0bbde8cfcdd7477032cd458/rapidfuzz-3.12.2-cp311-cp311-win32.whl", hash = "sha256:b4c5a0413589aef936892fbfa94b7ff6f7dd09edf19b5a7b83896cc9d4e8c184", size = 1863208 }, - { url = "https://files.pythonhosted.org/packages/38/4c/faacecf70a4e202a02f029ec6f6e04e910d95c4ef36d7d63b83b160f7f3e/rapidfuzz-3.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:58d9ae5cf9246d102db2a2558b67fe7e73c533e5d769099747921232d88b9be2", size = 1630876 }, - { url = "https://files.pythonhosted.org/packages/a7/4b/4931da26e0677880a9a533ef75ccbe564c091aa4a3579aed0355c7e06900/rapidfuzz-3.12.2-cp311-cp311-win_arm64.whl", hash = "sha256:7635fe34246cd241c8e35eb83084e978b01b83d5ef7e5bf72a704c637f270017", size = 870896 }, - { url = "https://files.pythonhosted.org/packages/a7/d2/e071753227c9e9f7f3550b983f30565f6e994581529815fa5a8879e7cd10/rapidfuzz-3.12.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1d982a651253ffe8434d9934ff0c1089111d60502228464721a2a4587435e159", size = 1944403 }, - { url = "https://files.pythonhosted.org/packages/aa/d1/4a10d21cc97aa36f4019af24382b5b4dc5ea6444499883c1c1286c6089ba/rapidfuzz-3.12.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02e6466caa0222d5233b1f05640873671cd99549a5c5ba4c29151634a1e56080", size = 1430287 }, - { url = "https://files.pythonhosted.org/packages/6a/2d/76d39ab0beeb884d432096fe288c41850e37608e0145264081d0cb809f3c/rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e956b3f053e474abae69ac693a52742109d860ac2375fe88e9387d3277f4c96c", size = 1403693 }, - { url = "https://files.pythonhosted.org/packages/85/1a/719b0f6498c003627e4b83b841bdcd48b11de8a9908a9051c4d2a0bc2245/rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dee7d740a2d5418d4f964f39ab8d89923e6b945850db833e798a1969b19542a", size = 5555878 }, - { url = "https://files.pythonhosted.org/packages/af/48/14d952a73254b4b0e517141acd27979bd23948adaf197f6ca2dc722fde6a/rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a057cdb0401e42c84b6516c9b1635f7aedd5e430c6e388bd5f6bcd1d6a0686bb", size = 1655301 }, - { url = "https://files.pythonhosted.org/packages/db/3f/b093e154e9752325d7459aa6dca43b7acbcaffa05133507e2403676e3e75/rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dccf8d4fb5b86d39c581a59463c596b1d09df976da26ff04ae219604223d502f", size = 1678069 }, - { url = "https://files.pythonhosted.org/packages/d6/7e/88853ecae5b5456eb1a1d8a01cbd534e25b671735d5d974609cbae082542/rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21d5b3793c6f5aecca595cd24164bf9d3c559e315ec684f912146fc4e769e367", size = 3137119 }, - { url = "https://files.pythonhosted.org/packages/4d/d2/b1f809b815aaf682ddac9c57929149f740b90feeb4f8da2f535c196de821/rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:46a616c0e13cff2de1761b011e0b14bb73b110182f009223f1453d505c9a975c", size = 2491639 }, - { url = "https://files.pythonhosted.org/packages/61/e4/a908d7b8db6e52ba2f80f6f0d0709ef9fdedb767db4307084331742b67f0/rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19fa5bc4301a1ee55400d4a38a8ecf9522b0391fc31e6da5f4d68513fe5c0026", size = 7821561 }, - { url = "https://files.pythonhosted.org/packages/f3/83/0250c49deefff15c46f5e590d8ee6abbd0f056e20b85994db55c16ac6ead/rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:544a47190a0d25971658a9365dba7095397b4ce3e897f7dd0a77ca2cf6fa984e", size = 2874048 }, - { url = "https://files.pythonhosted.org/packages/6c/3f/8d433d964c6e476476ee53eae5fa77b9f16b38d312eb1571e9099a6a3b12/rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f21af27c5e001f0ba1b88c36a0936437dfe034c452548d998891c21125eb640f", size = 3522801 }, - { url = "https://files.pythonhosted.org/packages/82/85/4931bfa41ef837b1544838e46e0556640d18114b3da9cf05e10defff00ae/rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b63170d9db00629b5b3f2862114d8d6ee19127eaba0eee43762d62a25817dbe0", size = 4567304 }, - { url = "https://files.pythonhosted.org/packages/b1/fe/fdae322869885115dd19a38c1da71b73a8832aa77757c93f460743d4f54c/rapidfuzz-3.12.2-cp312-cp312-win32.whl", hash = "sha256:6c7152d77b2eb6bfac7baa11f2a9c45fd5a2d848dbb310acd0953b3b789d95c9", size = 1845332 }, - { url = "https://files.pythonhosted.org/packages/ca/a4/2ccebda5fb8a266d163d57a42c2a6ef6f91815df5d89cf38c12e8aa6ed0b/rapidfuzz-3.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:1a314d170ee272ac87579f25a6cf8d16a031e1f7a7b07663434b41a1473bc501", size = 1617926 }, - { url = "https://files.pythonhosted.org/packages/a5/bc/aa8a4dc4ebff966dd039cce017c614cfd202049b4d1a2daafee7d018521b/rapidfuzz-3.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:d41e8231326e94fd07c4d8f424f6bed08fead6f5e6688d1e6e787f1443ae7631", size = 864737 }, - { url = "https://files.pythonhosted.org/packages/96/59/2ea3b5bb82798eae73d6ee892264ebfe42727626c1f0e96c77120f0d5cf6/rapidfuzz-3.12.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:941f31038dba5d3dedcfcceba81d61570ad457c873a24ceb13f4f44fcb574260", size = 1936870 }, - { url = "https://files.pythonhosted.org/packages/54/85/4e486bf9ea05e771ad231731305ed701db1339157f630b76b246ce29cf71/rapidfuzz-3.12.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fe2dfc454ee51ba168a67b1e92b72aad251e45a074972cef13340bbad2fd9438", size = 1424231 }, - { url = "https://files.pythonhosted.org/packages/dc/60/aeea3eed402c40a8cf055d554678769fbee0dd95c22f04546070a22bb90e/rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78fafaf7f5a48ee35ccd7928339080a0136e27cf97396de45259eca1d331b714", size = 1398055 }, - { url = "https://files.pythonhosted.org/packages/33/6b/757106f4c21fe3f20ce13ba3df560da60e52fe0dc390fd22bf613761669c/rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0c7989ff32c077bb8fd53253fd6ca569d1bfebc80b17557e60750e6909ba4fe", size = 5526188 }, - { url = "https://files.pythonhosted.org/packages/1e/a2/7c680cdc5532746dba67ecf302eed975252657094e50ae334fa9268352e8/rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96fa00bc105caa34b6cd93dca14a29243a3a7f0c336e4dcd36348d38511e15ac", size = 1648483 }, - { url = "https://files.pythonhosted.org/packages/f6/b0/ce942a1448b1a75d64af230dd746dede502224dd29ca9001665bbfd4bee6/rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bccfb30c668620c5bc3490f2dc7d7da1cca0ead5a9da8b755e2e02e2ef0dff14", size = 1676076 }, - { url = "https://files.pythonhosted.org/packages/ba/71/81f77b08333200be6984b6cdf2bdfd7cfca4943f16b478a2f7838cba8d66/rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f9b0adc3d894beb51f5022f64717b6114a6fabaca83d77e93ac7675911c8cc5", size = 3114169 }, - { url = "https://files.pythonhosted.org/packages/01/16/f3f34b207fdc8c61a33f9d2d61fc96b62c7dadca88bda1df1be4b94afb0b/rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32691aa59577f42864d5535cb6225d0f47e2c7bff59cf4556e5171e96af68cc1", size = 2485317 }, - { url = "https://files.pythonhosted.org/packages/b2/a6/b954f0766f644eb8dd8df44703e024ab4f5f15a8f8f5ea969963dd036f50/rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:758b10380ad34c1f51753a070d7bb278001b5e6fcf544121c6df93170952d705", size = 7844495 }, - { url = "https://files.pythonhosted.org/packages/fb/8f/1dc604d05e07150a02b56a8ffc47df75ce316c65467259622c9edf098451/rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:50a9c54c0147b468363119132d514c5024fbad1ed8af12bd8bd411b0119f9208", size = 2873242 }, - { url = "https://files.pythonhosted.org/packages/78/a9/9c649ace4b7f885e0a5fdcd1f33b057ebd83ecc2837693e6659bd944a2bb/rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e3ceb87c11d2d0fbe8559bb795b0c0604b84cfc8bb7b8720b5c16e9e31e00f41", size = 3519124 }, - { url = "https://files.pythonhosted.org/packages/f5/81/ce0b774e540a2e22ec802e383131d7ead18347197304d584c4ccf7b8861a/rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f7c9a003002434889255ff5676ca0f8934a478065ab5e702f75dc42639505bba", size = 4557831 }, - { url = "https://files.pythonhosted.org/packages/13/28/7bf0ee8d35efa7ab14e83d1795cdfd54833aa0428b6f87e987893136c372/rapidfuzz-3.12.2-cp313-cp313-win32.whl", hash = "sha256:cf165a76870cd875567941cf861dfd361a0a6e6a56b936c5d30042ddc9def090", size = 1842802 }, - { url = "https://files.pythonhosted.org/packages/ef/7e/792d609484776c8a40e1695ebd28b62196be9f8347b785b9104604dc7268/rapidfuzz-3.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:55bcc003541f5f16ec0a73bf6de758161973f9e8d75161954380738dd147f9f2", size = 1615808 }, - { url = "https://files.pythonhosted.org/packages/4b/43/ca3d1018b392f49131843648e10b08ace23afe8dad3bee5f136e4346b7cd/rapidfuzz-3.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:69f6ecdf1452139f2b947d0c169a605de578efdb72cbb2373cb0a94edca1fd34", size = 863535 }, - { url = "https://files.pythonhosted.org/packages/ee/4d/e910b70839d88d1c38ba806b0ddaa94b478cca8a09f4e7155b2b607c34b2/rapidfuzz-3.12.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b1e6f48e1ffa0749261ee23a1c6462bdd0be5eac83093f4711de17a42ae78ad", size = 1860425 }, - { url = "https://files.pythonhosted.org/packages/fd/62/54914f63e185539fbcca65acb1f7c879740a278d240527ed5ddd40bd7690/rapidfuzz-3.12.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ae9ded463f2ca4ba1eb762913c5f14c23d2e120739a62b7f4cc102eab32dc90", size = 1369066 }, - { url = "https://files.pythonhosted.org/packages/56/4a/de2cfab279497d0b2529d3fec398f60cf8e27a51d667b6529081fbdb0af2/rapidfuzz-3.12.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dda45f47b559be72ecbce45c7f71dc7c97b9772630ab0f3286d97d2c3025ab71", size = 1365330 }, - { url = "https://files.pythonhosted.org/packages/dd/48/170c37cfdf04efa34e7cafc688a8517c9098c1d27e1513393ad71bf3165c/rapidfuzz-3.12.2-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3745c6443890265513a3c8777f2de4cb897aeb906a406f97741019be8ad5bcc", size = 5481251 }, - { url = "https://files.pythonhosted.org/packages/4e/2d/107c489443f6438780d2e40747d5880c8d9374a64e17487eb4085fe7f1f5/rapidfuzz-3.12.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36d3ef4f047ed1bc96fa29289f9e67a637ddca5e4f4d3dc7cb7f50eb33ec1664", size = 3060633 }, - { url = "https://files.pythonhosted.org/packages/09/f6/fa777f336629aee8938f3d5c95c09df38459d4eadbdbe34642889857fb6a/rapidfuzz-3.12.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:54bb69ebe5ca0bd7527357e348f16a4c0c52fe0c2fcc8a041010467dcb8385f7", size = 1555000 }, +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/6895abc3a3d056b9698da3199b04c0e56226d530ae44a470edabf8b664f0/rapidfuzz-3.13.0.tar.gz", hash = "sha256:d2eaf3839e52cbcc0accbe9817a67b4b0fcf70aaeb229cfddc1c28061f9ce5d8", size = 57904226 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/17/9be9eff5a3c7dfc831c2511262082c6786dca2ce21aa8194eef1cb71d67a/rapidfuzz-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d395a5cad0c09c7f096433e5fd4224d83b53298d53499945a9b0e5a971a84f3a", size = 1999453 }, + { url = "https://files.pythonhosted.org/packages/75/67/62e57896ecbabe363f027d24cc769d55dd49019e576533ec10e492fcd8a2/rapidfuzz-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7b3eda607a019169f7187328a8d1648fb9a90265087f6903d7ee3a8eee01805", size = 1450881 }, + { url = "https://files.pythonhosted.org/packages/96/5c/691c5304857f3476a7b3df99e91efc32428cbe7d25d234e967cc08346c13/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98e0bfa602e1942d542de077baf15d658bd9d5dcfe9b762aff791724c1c38b70", size = 1422990 }, + { url = "https://files.pythonhosted.org/packages/46/81/7a7e78f977496ee2d613154b86b203d373376bcaae5de7bde92f3ad5a192/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bef86df6d59667d9655905b02770a0c776d2853971c0773767d5ef8077acd624", size = 5342309 }, + { url = "https://files.pythonhosted.org/packages/51/44/12fdd12a76b190fe94bf38d252bb28ddf0ab7a366b943e792803502901a2/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fedd316c165beed6307bf754dee54d3faca2c47e1f3bcbd67595001dfa11e969", size = 1656881 }, + { url = "https://files.pythonhosted.org/packages/27/ae/0d933e660c06fcfb087a0d2492f98322f9348a28b2cc3791a5dbadf6e6fb/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5158da7f2ec02a930be13bac53bb5903527c073c90ee37804090614cab83c29e", size = 1608494 }, + { url = "https://files.pythonhosted.org/packages/3d/2c/4b2f8aafdf9400e5599b6ed2f14bc26ca75f5a923571926ccbc998d4246a/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b6f913ee4618ddb6d6f3e387b76e8ec2fc5efee313a128809fbd44e65c2bbb2", size = 3072160 }, + { url = "https://files.pythonhosted.org/packages/60/7d/030d68d9a653c301114101c3003b31ce01cf2c3224034cd26105224cd249/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d25fdbce6459ccbbbf23b4b044f56fbd1158b97ac50994eaae2a1c0baae78301", size = 2491549 }, + { url = "https://files.pythonhosted.org/packages/8e/cd/7040ba538fc6a8ddc8816a05ecf46af9988b46c148ddd7f74fb0fb73d012/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25343ccc589a4579fbde832e6a1e27258bfdd7f2eb0f28cb836d6694ab8591fc", size = 7584142 }, + { url = "https://files.pythonhosted.org/packages/c1/96/85f7536fbceb0aa92c04a1c37a3fc4fcd4e80649e9ed0fb585382df82edc/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a9ad1f37894e3ffb76bbab76256e8a8b789657183870be11aa64e306bb5228fd", size = 2896234 }, + { url = "https://files.pythonhosted.org/packages/55/fd/460e78438e7019f2462fe9d4ecc880577ba340df7974c8a4cfe8d8d029df/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5dc71ef23845bb6b62d194c39a97bb30ff171389c9812d83030c1199f319098c", size = 3437420 }, + { url = "https://files.pythonhosted.org/packages/cc/df/c3c308a106a0993befd140a414c5ea78789d201cf1dfffb8fd9749718d4f/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b7f4c65facdb94f44be759bbd9b6dda1fa54d0d6169cdf1a209a5ab97d311a75", size = 4410860 }, + { url = "https://files.pythonhosted.org/packages/75/ee/9d4ece247f9b26936cdeaae600e494af587ce9bf8ddc47d88435f05cfd05/rapidfuzz-3.13.0-cp311-cp311-win32.whl", hash = "sha256:b5104b62711565e0ff6deab2a8f5dbf1fbe333c5155abe26d2cfd6f1849b6c87", size = 1843161 }, + { url = "https://files.pythonhosted.org/packages/c9/5a/d00e1f63564050a20279015acb29ecaf41646adfacc6ce2e1e450f7f2633/rapidfuzz-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:9093cdeb926deb32a4887ebe6910f57fbcdbc9fbfa52252c10b56ef2efb0289f", size = 1629962 }, + { url = "https://files.pythonhosted.org/packages/3b/74/0a3de18bc2576b794f41ccd07720b623e840fda219ab57091897f2320fdd/rapidfuzz-3.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:f70f646751b6aa9d05be1fb40372f006cc89d6aad54e9d79ae97bd1f5fce5203", size = 866631 }, + { url = "https://files.pythonhosted.org/packages/13/4b/a326f57a4efed8f5505b25102797a58e37ee11d94afd9d9422cb7c76117e/rapidfuzz-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a1a6a906ba62f2556372282b1ef37b26bca67e3d2ea957277cfcefc6275cca7", size = 1989501 }, + { url = "https://files.pythonhosted.org/packages/b7/53/1f7eb7ee83a06c400089ec7cb841cbd581c2edd7a4b21eb2f31030b88daa/rapidfuzz-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fd0975e015b05c79a97f38883a11236f5a24cca83aa992bd2558ceaa5652b26", size = 1445379 }, + { url = "https://files.pythonhosted.org/packages/07/09/de8069a4599cc8e6d194e5fa1782c561151dea7d5e2741767137e2a8c1f0/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d4e13593d298c50c4f94ce453f757b4b398af3fa0fd2fde693c3e51195b7f69", size = 1405986 }, + { url = "https://files.pythonhosted.org/packages/5d/77/d9a90b39c16eca20d70fec4ca377fbe9ea4c0d358c6e4736ab0e0e78aaf6/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed6f416bda1c9133000009d84d9409823eb2358df0950231cc936e4bf784eb97", size = 5310809 }, + { url = "https://files.pythonhosted.org/packages/1e/7d/14da291b0d0f22262d19522afaf63bccf39fc027c981233fb2137a57b71f/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dc82b6ed01acb536b94a43996a94471a218f4d89f3fdd9185ab496de4b2a981", size = 1629394 }, + { url = "https://files.pythonhosted.org/packages/b7/e4/79ed7e4fa58f37c0f8b7c0a62361f7089b221fe85738ae2dbcfb815e985a/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9d824de871daa6e443b39ff495a884931970d567eb0dfa213d234337343835f", size = 1600544 }, + { url = "https://files.pythonhosted.org/packages/4e/20/e62b4d13ba851b0f36370060025de50a264d625f6b4c32899085ed51f980/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d18228a2390375cf45726ce1af9d36ff3dc1f11dce9775eae1f1b13ac6ec50f", size = 3052796 }, + { url = "https://files.pythonhosted.org/packages/cd/8d/55fdf4387dec10aa177fe3df8dbb0d5022224d95f48664a21d6b62a5299d/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5fe634c9482ec5d4a6692afb8c45d370ae86755e5f57aa6c50bfe4ca2bdd87", size = 2464016 }, + { url = "https://files.pythonhosted.org/packages/9b/be/0872f6a56c0f473165d3b47d4170fa75263dc5f46985755aa9bf2bbcdea1/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:694eb531889f71022b2be86f625a4209c4049e74be9ca836919b9e395d5e33b3", size = 7556725 }, + { url = "https://files.pythonhosted.org/packages/5d/f3/6c0750e484d885a14840c7a150926f425d524982aca989cdda0bb3bdfa57/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11b47b40650e06147dee5e51a9c9ad73bb7b86968b6f7d30e503b9f8dd1292db", size = 2859052 }, + { url = "https://files.pythonhosted.org/packages/6f/98/5a3a14701b5eb330f444f7883c9840b43fb29c575e292e09c90a270a6e07/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:98b8107ff14f5af0243f27d236bcc6e1ef8e7e3b3c25df114e91e3a99572da73", size = 3390219 }, + { url = "https://files.pythonhosted.org/packages/e9/7d/f4642eaaeb474b19974332f2a58471803448be843033e5740965775760a5/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b836f486dba0aceb2551e838ff3f514a38ee72b015364f739e526d720fdb823a", size = 4377924 }, + { url = "https://files.pythonhosted.org/packages/8e/83/fa33f61796731891c3e045d0cbca4436a5c436a170e7f04d42c2423652c3/rapidfuzz-3.13.0-cp312-cp312-win32.whl", hash = "sha256:4671ee300d1818d7bdfd8fa0608580d7778ba701817216f0c17fb29e6b972514", size = 1823915 }, + { url = "https://files.pythonhosted.org/packages/03/25/5ee7ab6841ca668567d0897905eebc79c76f6297b73bf05957be887e9c74/rapidfuzz-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e2065f68fb1d0bf65adc289c1bdc45ba7e464e406b319d67bb54441a1b9da9e", size = 1616985 }, + { url = "https://files.pythonhosted.org/packages/76/5e/3f0fb88db396cb692aefd631e4805854e02120a2382723b90dcae720bcc6/rapidfuzz-3.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:65cc97c2fc2c2fe23586599686f3b1ceeedeca8e598cfcc1b7e56dc8ca7e2aa7", size = 860116 }, + { url = "https://files.pythonhosted.org/packages/0a/76/606e71e4227790750f1646f3c5c873e18d6cfeb6f9a77b2b8c4dec8f0f66/rapidfuzz-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:09e908064d3684c541d312bd4c7b05acb99a2c764f6231bd507d4b4b65226c23", size = 1982282 }, + { url = "https://files.pythonhosted.org/packages/0a/f5/d0b48c6b902607a59fd5932a54e3518dae8223814db8349b0176e6e9444b/rapidfuzz-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57c390336cb50d5d3bfb0cfe1467478a15733703af61f6dffb14b1cd312a6fae", size = 1439274 }, + { url = "https://files.pythonhosted.org/packages/59/cf/c3ac8c80d8ced6c1f99b5d9674d397ce5d0e9d0939d788d67c010e19c65f/rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0da54aa8547b3c2c188db3d1c7eb4d1bb6dd80baa8cdaeaec3d1da3346ec9caa", size = 1399854 }, + { url = "https://files.pythonhosted.org/packages/09/5d/ca8698e452b349c8313faf07bfa84e7d1c2d2edf7ccc67bcfc49bee1259a/rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df8e8c21e67afb9d7fbe18f42c6111fe155e801ab103c81109a61312927cc611", size = 5308962 }, + { url = "https://files.pythonhosted.org/packages/66/0a/bebada332854e78e68f3d6c05226b23faca79d71362509dbcf7b002e33b7/rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:461fd13250a2adf8e90ca9a0e1e166515cbcaa5e9c3b1f37545cbbeff9e77f6b", size = 1625016 }, + { url = "https://files.pythonhosted.org/packages/de/0c/9e58d4887b86d7121d1c519f7050d1be5eb189d8a8075f5417df6492b4f5/rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2b3dd5d206a12deca16870acc0d6e5036abeb70e3cad6549c294eff15591527", size = 1600414 }, + { url = "https://files.pythonhosted.org/packages/9b/df/6096bc669c1311568840bdcbb5a893edc972d1c8d2b4b4325c21d54da5b1/rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1343d745fbf4688e412d8f398c6e6d6f269db99a54456873f232ba2e7aeb4939", size = 3053179 }, + { url = "https://files.pythonhosted.org/packages/f9/46/5179c583b75fce3e65a5cd79a3561bd19abd54518cb7c483a89b284bf2b9/rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b1b065f370d54551dcc785c6f9eeb5bd517ae14c983d2784c064b3aa525896df", size = 2456856 }, + { url = "https://files.pythonhosted.org/packages/6b/64/e9804212e3286d027ac35bbb66603c9456c2bce23f823b67d2f5cabc05c1/rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:11b125d8edd67e767b2295eac6eb9afe0b1cdc82ea3d4b9257da4b8e06077798", size = 7567107 }, + { url = "https://files.pythonhosted.org/packages/8a/f2/7d69e7bf4daec62769b11757ffc31f69afb3ce248947aadbb109fefd9f65/rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c33f9c841630b2bb7e69a3fb5c84a854075bb812c47620978bddc591f764da3d", size = 2854192 }, + { url = "https://files.pythonhosted.org/packages/05/21/ab4ad7d7d0f653e6fe2e4ccf11d0245092bef94cdff587a21e534e57bda8/rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae4574cb66cf1e85d32bb7e9ec45af5409c5b3970b7ceb8dea90168024127566", size = 3398876 }, + { url = "https://files.pythonhosted.org/packages/0f/a8/45bba94c2489cb1ee0130dcb46e1df4fa2c2b25269e21ffd15240a80322b/rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e05752418b24bbd411841b256344c26f57da1148c5509e34ea39c7eb5099ab72", size = 4377077 }, + { url = "https://files.pythonhosted.org/packages/0c/f3/5e0c6ae452cbb74e5436d3445467447e8c32f3021f48f93f15934b8cffc2/rapidfuzz-3.13.0-cp313-cp313-win32.whl", hash = "sha256:0e1d08cb884805a543f2de1f6744069495ef527e279e05370dd7c83416af83f8", size = 1822066 }, + { url = "https://files.pythonhosted.org/packages/96/e3/a98c25c4f74051df4dcf2f393176b8663bfd93c7afc6692c84e96de147a2/rapidfuzz-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9a7c6232be5f809cd39da30ee5d24e6cadd919831e6020ec6c2391f4c3bc9264", size = 1615100 }, + { url = "https://files.pythonhosted.org/packages/60/b1/05cd5e697c00cd46d7791915f571b38c8531f714832eff2c5e34537c49ee/rapidfuzz-3.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:3f32f15bacd1838c929b35c84b43618481e1b3d7a61b5ed2db0291b70ae88b53", size = 858976 }, + { url = "https://files.pythonhosted.org/packages/88/df/6060c5a9c879b302bd47a73fc012d0db37abf6544c57591bcbc3459673bd/rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1ba007f4d35a45ee68656b2eb83b8715e11d0f90e5b9f02d615a8a321ff00c27", size = 1905935 }, + { url = "https://files.pythonhosted.org/packages/a2/6c/a0b819b829e20525ef1bd58fc776fb8d07a0c38d819e63ba2b7c311a2ed4/rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d7a217310429b43be95b3b8ad7f8fc41aba341109dc91e978cd7c703f928c58f", size = 1383714 }, + { url = "https://files.pythonhosted.org/packages/6a/c1/3da3466cc8a9bfb9cd345ad221fac311143b6a9664b5af4adb95b5e6ce01/rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:558bf526bcd777de32b7885790a95a9548ffdcce68f704a81207be4a286c1095", size = 1367329 }, + { url = "https://files.pythonhosted.org/packages/da/f0/9f2a9043bfc4e66da256b15d728c5fc2d865edf0028824337f5edac36783/rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:202a87760f5145140d56153b193a797ae9338f7939eb16652dd7ff96f8faf64c", size = 5251057 }, + { url = "https://files.pythonhosted.org/packages/6a/ff/af2cb1d8acf9777d52487af5c6b34ce9d13381a753f991d95ecaca813407/rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcccc08f671646ccb1e413c773bb92e7bba789e3a1796fd49d23c12539fe2e4", size = 2992401 }, + { url = "https://files.pythonhosted.org/packages/c1/c5/c243b05a15a27b946180db0d1e4c999bef3f4221505dff9748f1f6c917be/rapidfuzz-3.13.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f219f1e3c3194d7a7de222f54450ce12bc907862ff9a8962d83061c1f923c86", size = 1553782 }, ] [[package]] @@ -873,7 +1281,7 @@ socks = [ [[package]] name = "rerun-sdk" -version = "0.22.1" +version = "0.23.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -883,24 +1291,24 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/41/f9724c6ee4b4d45f9ae433f41fc8b4f2827d5b3f9459b9e9a9c55fdda5ab/rerun_sdk-0.22.1-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:176853d14bcac0b3cab8b240fe6760e32a577b1edd9ecb2fd0656e83f046a2c4", size = 46819325 }, - { url = "https://files.pythonhosted.org/packages/01/8e/db9fd7e7d9d9ec6e3f0781ce6f3b71c06952f9b0c40e9720822fd83f711e/rerun_sdk-0.22.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:dea4a50c916bc82bd97a8f9dc44c71f9a9fccec7e73f37edfaa0de800caf5dce", size = 44566672 }, - { url = "https://files.pythonhosted.org/packages/ad/41/87079d0a897e4fc2dd54a356be9556626f40ec1ff1c3393178e7ac9feb9d/rerun_sdk-0.22.1-cp38-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:29d807909f5e484aa6427d9fb25706d0a154de392a4ca47eb303fbd135c0e382", size = 49830061 }, - { url = "https://files.pythonhosted.org/packages/1e/db/3ce2be017d7d4ac0948fb064bf7417b36c96bc07d1b8a922017606ba03c6/rerun_sdk-0.22.1-cp38-abi3-manylinux_2_31_x86_64.whl", hash = "sha256:b9672412d0cdf57c79c10ca683e59716399da3358df78b985b25093d022ebbaf", size = 51423327 }, - { url = "https://files.pythonhosted.org/packages/43/eb/edafdcba1619955c2c9b66c7a1bcbbce4ad62a1a36db984b46aa793fe366/rerun_sdk-0.22.1-cp38-abi3-win_amd64.whl", hash = "sha256:d3d0afe1b8e749a1088a0bb75f372ad7fe70eb3728bd32e07bf78300436a053d", size = 42085149 }, + { url = "https://files.pythonhosted.org/packages/dd/6e/a125f4fe2de3269f443b7cb65d465ffd37a836a2dac7e4318e21239d78c8/rerun_sdk-0.23.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:fe06d21cfcf4d84a9396f421d4779efabec7e9674d232a2c552c8a91d871c375", size = 66094053 }, + { url = "https://files.pythonhosted.org/packages/55/f6/b6d13322b05dc77bd9a0127e98155c2b7ee987a236fd4d331eed2e547a90/rerun_sdk-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:823ae87bfa644e06fb70bada08a83690dd23d9824a013947f80a22c6731bdc0d", size = 62047843 }, + { url = "https://files.pythonhosted.org/packages/a5/7f/6a7422cb727e14a65b55b0089988eeea8d0532c429397a863e6ba395554a/rerun_sdk-0.23.1-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:dc5129f8744f71249bf45558c853422c51ef39b6b5eea0ea1f602c6049ce732f", size = 68214509 }, + { url = "https://files.pythonhosted.org/packages/4f/86/3aee9eadbfe55188a2c7d739378545b4319772a4d3b165e8d3fc598fa630/rerun_sdk-0.23.1-cp39-abi3-manylinux_2_31_x86_64.whl", hash = "sha256:ee0d0e17df0e08be13b77cc74884c5d8ba8edb39b6f5a60dc2429d39033d90f6", size = 71442196 }, + { url = "https://files.pythonhosted.org/packages/a7/ba/028bd382e2ae21e6643cec25f423285dbc6b328ce56d55727b4101ef9443/rerun_sdk-0.23.1-cp39-abi3-win_amd64.whl", hash = "sha256:d4273db55b56310b053a2de6bf5927a8692cf65f4d234c6e6928fb24ed8a960d", size = 57583198 }, ] [[package]] name = "rich" -version = "13.9.4" +version = "14.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, ] [[package]] @@ -919,7 +1327,7 @@ wheels = [ [[package]] name = "rosbags" -version = "0.10.8" +version = "0.10.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lz4" }, @@ -928,9 +1336,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/fc/cdac914a619648113428571512a9c272ba55ca37da37bb969db9ba91affc/rosbags-0.10.8.tar.gz", hash = "sha256:6e91f4d95ef5a25c8640a6e3e11161bda682fae5ef6bdb17357cda0bd2efc0bf", size = 223525 } +sdist = { url = "https://files.pythonhosted.org/packages/21/f9/a5dfa12d759178b291908ea4fa7e25f1ca4889b4b3b30e93dd2fb3905bef/rosbags-0.10.9.tar.gz", hash = "sha256:38fc1999370f6c173765c827b230f7486137fdd43c134b116374ad0fe264a135", size = 227641 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/16/0d679c29747732d84a709ff825b1fe620526f3779ee2fec4cfcbe265be01/rosbags-0.10.8-py3-none-any.whl", hash = "sha256:acb34fad78b87133201079f4687ee6d3ad8fbac87acf3da25d5b017eef795b80", size = 131782 }, + { url = "https://files.pythonhosted.org/packages/af/22/b7644271253fdc8bf86f62ed46db538df644d474c2c0f0eeecb2b47507a7/rosbags-0.10.9-py3-none-any.whl", hash = "sha256:3b0f4e4821af69921aff4217432ffe8670ad245ab60198dbfcc3b578e83990ac", size = 135402 }, ] [[package]] @@ -982,27 +1390,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/2b/7ca27e854d92df5e681e6527dc0f9254c9dc06c8408317893cf96c851cdd/ruff-0.11.0.tar.gz", hash = "sha256:e55c620690a4a7ee6f1cccb256ec2157dc597d109400ae75bbf944fc9d6462e2", size = 3799407 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/40/3d0340a9e5edc77d37852c0cd98c5985a5a8081fc3befaeb2ae90aaafd2b/ruff-0.11.0-py3-none-linux_armv6l.whl", hash = "sha256:dc67e32bc3b29557513eb7eeabb23efdb25753684b913bebb8a0c62495095acb", size = 10098158 }, - { url = "https://files.pythonhosted.org/packages/ec/a9/d8f5abb3b87b973b007649ac7bf63665a05b2ae2b2af39217b09f52abbbf/ruff-0.11.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38c23fd9bdec4eb437b4c1e3595905a0a8edfccd63a790f818b28c78fe345639", size = 10879071 }, - { url = "https://files.pythonhosted.org/packages/ab/62/aaa198614c6211677913ec480415c5e6509586d7b796356cec73a2f8a3e6/ruff-0.11.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c8661b0be91a38bd56db593e9331beaf9064a79028adee2d5f392674bbc5e88", size = 10247944 }, - { url = "https://files.pythonhosted.org/packages/9f/52/59e0a9f2cf1ce5e6cbe336b6dd0144725c8ea3b97cac60688f4e7880bf13/ruff-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c0e8d3d2db7e9f6efd884f44b8dc542d5b6b590fc4bb334fdbc624d93a29a2", size = 10421725 }, - { url = "https://files.pythonhosted.org/packages/a6/c3/dcd71acc6dff72ce66d13f4be5bca1dbed4db678dff2f0f6f307b04e5c02/ruff-0.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c3156d3f4b42e57247275a0a7e15a851c165a4fc89c5e8fa30ea6da4f7407b8", size = 9954435 }, - { url = "https://files.pythonhosted.org/packages/a6/9a/342d336c7c52dbd136dee97d4c7797e66c3f92df804f8f3b30da59b92e9c/ruff-0.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:490b1e147c1260545f6d041c4092483e3f6d8eba81dc2875eaebcf9140b53905", size = 11492664 }, - { url = "https://files.pythonhosted.org/packages/84/35/6e7defd2d7ca95cc385ac1bd9f7f2e4a61b9cc35d60a263aebc8e590c462/ruff-0.11.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1bc09a7419e09662983b1312f6fa5dab829d6ab5d11f18c3760be7ca521c9329", size = 12207856 }, - { url = "https://files.pythonhosted.org/packages/22/78/da669c8731bacf40001c880ada6d31bcfb81f89cc996230c3b80d319993e/ruff-0.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfa478daf61ac8002214eb2ca5f3e9365048506a9d52b11bea3ecea822bb844", size = 11645156 }, - { url = "https://files.pythonhosted.org/packages/ee/47/e27d17d83530a208f4a9ab2e94f758574a04c51e492aa58f91a3ed7cbbcb/ruff-0.11.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fbb2aed66fe742a6a3a0075ed467a459b7cedc5ae01008340075909d819df1e", size = 13884167 }, - { url = "https://files.pythonhosted.org/packages/9f/5e/42ffbb0a5d4b07bbc642b7d58357b4e19a0f4774275ca6ca7d1f7b5452cd/ruff-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c0c1ff014351c0b0cdfdb1e35fa83b780f1e065667167bb9502d47ca41e6db", size = 11348311 }, - { url = "https://files.pythonhosted.org/packages/c8/51/dc3ce0c5ce1a586727a3444a32f98b83ba99599bb1ebca29d9302886e87f/ruff-0.11.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e4fd5ff5de5f83e0458a138e8a869c7c5e907541aec32b707f57cf9a5e124445", size = 10305039 }, - { url = "https://files.pythonhosted.org/packages/60/e0/475f0c2f26280f46f2d6d1df1ba96b3399e0234cf368cc4c88e6ad10dcd9/ruff-0.11.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:96bc89a5c5fd21a04939773f9e0e276308be0935de06845110f43fd5c2e4ead7", size = 9937939 }, - { url = "https://files.pythonhosted.org/packages/e2/d3/3e61b7fd3e9cdd1e5b8c7ac188bec12975c824e51c5cd3d64caf81b0331e/ruff-0.11.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a9352b9d767889ec5df1483f94870564e8102d4d7e99da52ebf564b882cdc2c7", size = 10923259 }, - { url = "https://files.pythonhosted.org/packages/30/32/cd74149ebb40b62ddd14bd2d1842149aeb7f74191fb0f49bd45c76909ff2/ruff-0.11.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:049a191969a10897fe052ef9cc7491b3ef6de79acd7790af7d7897b7a9bfbcb6", size = 11406212 }, - { url = "https://files.pythonhosted.org/packages/00/ef/033022a6b104be32e899b00de704d7c6d1723a54d4c9e09d147368f14b62/ruff-0.11.0-py3-none-win32.whl", hash = "sha256:3191e9116b6b5bbe187447656f0c8526f0d36b6fd89ad78ccaad6bdc2fad7df2", size = 10310905 }, - { url = "https://files.pythonhosted.org/packages/ed/8a/163f2e78c37757d035bd56cd60c8d96312904ca4a6deeab8442d7b3cbf89/ruff-0.11.0-py3-none-win_amd64.whl", hash = "sha256:c58bfa00e740ca0a6c43d41fb004cd22d165302f360aaa56f7126d544db31a21", size = 11411730 }, - { url = "https://files.pythonhosted.org/packages/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956 }, +version = "0.11.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/89/6f9c9674818ac2e9cc2f2b35b704b7768656e6b7c139064fc7ba8fbc99f1/ruff-0.11.7.tar.gz", hash = "sha256:655089ad3224070736dc32844fde783454f8558e71f501cb207485fe4eee23d4", size = 4054861 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/ec/21927cb906c5614b786d1621dba405e3d44f6e473872e6df5d1a6bca0455/ruff-0.11.7-py3-none-linux_armv6l.whl", hash = "sha256:d29e909d9a8d02f928d72ab7837b5cbc450a5bdf578ab9ebee3263d0a525091c", size = 10245403 }, + { url = "https://files.pythonhosted.org/packages/e2/af/fec85b6c2c725bcb062a354dd7cbc1eed53c33ff3aa665165871c9c16ddf/ruff-0.11.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dd1fb86b168ae349fb01dd497d83537b2c5541fe0626e70c786427dd8363aaee", size = 11007166 }, + { url = "https://files.pythonhosted.org/packages/31/9a/2d0d260a58e81f388800343a45898fd8df73c608b8261c370058b675319a/ruff-0.11.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d3d7d2e140a6fbbc09033bce65bd7ea29d6a0adeb90b8430262fbacd58c38ada", size = 10378076 }, + { url = "https://files.pythonhosted.org/packages/c2/c4/9b09b45051404d2e7dd6d9dbcbabaa5ab0093f9febcae664876a77b9ad53/ruff-0.11.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4809df77de390a1c2077d9b7945d82f44b95d19ceccf0c287c56e4dc9b91ca64", size = 10557138 }, + { url = "https://files.pythonhosted.org/packages/5e/5e/f62a1b6669870a591ed7db771c332fabb30f83c967f376b05e7c91bccd14/ruff-0.11.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3a0c2e169e6b545f8e2dba185eabbd9db4f08880032e75aa0e285a6d3f48201", size = 10095726 }, + { url = "https://files.pythonhosted.org/packages/45/59/a7aa8e716f4cbe07c3500a391e58c52caf665bb242bf8be42c62adef649c/ruff-0.11.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49b888200a320dd96a68e86736cf531d6afba03e4f6cf098401406a257fcf3d6", size = 11672265 }, + { url = "https://files.pythonhosted.org/packages/dd/e3/101a8b707481f37aca5f0fcc3e42932fa38b51add87bfbd8e41ab14adb24/ruff-0.11.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2b19cdb9cf7dae00d5ee2e7c013540cdc3b31c4f281f1dacb5a799d610e90db4", size = 12331418 }, + { url = "https://files.pythonhosted.org/packages/dd/71/037f76cbe712f5cbc7b852e4916cd3cf32301a30351818d32ab71580d1c0/ruff-0.11.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64e0ee994c9e326b43539d133a36a455dbaab477bc84fe7bfbd528abe2f05c1e", size = 11794506 }, + { url = "https://files.pythonhosted.org/packages/ca/de/e450b6bab1fc60ef263ef8fcda077fb4977601184877dce1c59109356084/ruff-0.11.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bad82052311479a5865f52c76ecee5d468a58ba44fb23ee15079f17dd4c8fd63", size = 13939084 }, + { url = "https://files.pythonhosted.org/packages/0e/2c/1e364cc92970075d7d04c69c928430b23e43a433f044474f57e425cbed37/ruff-0.11.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7940665e74e7b65d427b82bffc1e46710ec7f30d58b4b2d5016e3f0321436502", size = 11450441 }, + { url = "https://files.pythonhosted.org/packages/9d/7d/1b048eb460517ff9accd78bca0fa6ae61df2b276010538e586f834f5e402/ruff-0.11.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:169027e31c52c0e36c44ae9a9c7db35e505fee0b39f8d9fca7274a6305295a92", size = 10441060 }, + { url = "https://files.pythonhosted.org/packages/3a/57/8dc6ccfd8380e5ca3d13ff7591e8ba46a3b330323515a4996b991b10bd5d/ruff-0.11.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:305b93f9798aee582e91e34437810439acb28b5fc1fee6b8205c78c806845a94", size = 10058689 }, + { url = "https://files.pythonhosted.org/packages/23/bf/20487561ed72654147817885559ba2aa705272d8b5dee7654d3ef2dbf912/ruff-0.11.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a681db041ef55550c371f9cd52a3cf17a0da4c75d6bd691092dfc38170ebc4b6", size = 11073703 }, + { url = "https://files.pythonhosted.org/packages/9d/27/04f2db95f4ef73dccedd0c21daf9991cc3b7f29901a4362057b132075aa4/ruff-0.11.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:07f1496ad00a4a139f4de220b0c97da6d4c85e0e4aa9b2624167b7d4d44fd6b6", size = 11532822 }, + { url = "https://files.pythonhosted.org/packages/e1/72/43b123e4db52144c8add336581de52185097545981ff6e9e58a21861c250/ruff-0.11.7-py3-none-win32.whl", hash = "sha256:f25dfb853ad217e6e5f1924ae8a5b3f6709051a13e9dad18690de6c8ff299e26", size = 10362436 }, + { url = "https://files.pythonhosted.org/packages/c5/a0/3e58cd76fdee53d5c8ce7a56d84540833f924ccdf2c7d657cb009e604d82/ruff-0.11.7-py3-none-win_amd64.whl", hash = "sha256:0a931d85959ceb77e92aea4bbedfded0a31534ce191252721128f77e5ae1f98a", size = 11566676 }, + { url = "https://files.pythonhosted.org/packages/68/ca/69d7c7752bce162d1516e5592b1cc6b6668e9328c0d270609ddbeeadd7cf/ruff-0.11.7-py3-none-win_arm64.whl", hash = "sha256:778c1e5d6f9e91034142dfd06110534ca13220bfaad5c3735f6cb844654f6177", size = 10677936 }, ] [[package]] @@ -1054,14 +1462,14 @@ wheels = [ [[package]] name = "scipy-stubs" -version = "1.15.2.1" +version = "1.15.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "optype" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/51/3d6dd7233d8fe19063cf0f4280fabbf5a0b0eaf270438808ca96b57eb118/scipy_stubs-1.15.2.1.tar.gz", hash = "sha256:ba9590ef3cd24511dced121ac0a9c8d9d0681ca5ac091128f0f7ba5a936a8693", size = 274081 } +sdist = { url = "https://files.pythonhosted.org/packages/61/ee/0c3e93545b53d3b22e662fbe251c3e61c2b14742f296f36708eff4a2898c/scipy_stubs-1.15.2.2.tar.gz", hash = "sha256:0137d907d75381d2eda4f6af5b1d3211759cb193a0eadf5195716fb0b01ca3cb", size = 275755 } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/9a/1019c6177105d8a26f9b29ad17b280c4d9495eedcd78b819ef8cee6a7c28/scipy_stubs-1.15.2.1-py3-none-any.whl", hash = "sha256:10977d731ec9b3fbff95dd5868903c9cd1caf8621f4ff8b3b45ef30148337bde", size = 458329 }, + { url = "https://files.pythonhosted.org/packages/2b/1a/3eba813584e398d589e1d4e0dac0cf822ce9e25b28cb2d1f0012d137c968/scipy_stubs-1.15.2.2-py3-none-any.whl", hash = "sha256:f02fe66124b58bce5f0897ecd48d0e79226a999cc4e6984a9472520c20b8e4b6", size = 459133 }, ] [[package]] @@ -1073,6 +1481,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, ] +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -1084,11 +1501,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.6" +version = "2.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677 }, ] [[package]] @@ -1114,7 +1531,7 @@ wheels = [ [[package]] name = "typer" -version = "0.15.2" +version = "0.15.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1122,30 +1539,30 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } +sdist = { url = "https://files.pythonhosted.org/packages/98/1a/5f36851f439884bcfe8539f6a20ff7516e7b60f319bbaf69a90dc35cc2eb/typer-0.15.3.tar.gz", hash = "sha256:818873625d0569653438316567861899f7e9972f2e6e0c16dab608345ced713c", size = 101641 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, + { url = "https://files.pythonhosted.org/packages/48/20/9d953de6f4367163d23ec823200eb3ecb0050a2609691e512c8b95827a9b/typer-0.15.3-py3-none-any.whl", hash = "sha256:c86a65ad77ca531f03de08d1b9cb67cd09ad02ddddf4b34745b5008f43b239bd", size = 45253 }, ] [[package]] name = "types-pyyaml" -version = "6.0.12.20241230" +version = "6.0.12.20250402" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/f9/4d566925bcf9396136c0a2e5dc7e230ff08d86fa011a69888dd184469d80/types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c", size = 17078 } +sdist = { url = "https://files.pythonhosted.org/packages/2d/68/609eed7402f87c9874af39d35942744e39646d1ea9011765ec87b01b2a3c/types_pyyaml-6.0.12.20250402.tar.gz", hash = "sha256:d7c13c3e6d335b6af4b0122a01ff1d270aba84ab96d1a1a1063ecba3e13ec075", size = 17282 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/c1/48474fbead512b70ccdb4f81ba5eb4a58f69d100ba19f17c92c0c4f50ae6/types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6", size = 20029 }, + { url = "https://files.pythonhosted.org/packages/ed/56/1fe61db05685fbb512c07ea9323f06ea727125951f1eb4dff110b3311da3/types_pyyaml-6.0.12.20250402-py3-none-any.whl", hash = "sha256:652348fa9e7a203d4b0d21066dfb00760d3cbd5a15ebb7cf8d33c88a49546681", size = 20329 }, ] [[package]] name = "types-requests" -version = "2.32.0.20250306" +version = "2.32.0.20250328" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/1a/beaeff79ef9efd186566ba5f0d95b44ae21f6d31e9413bcfbef3489b6ae3/types_requests-2.32.0.20250306.tar.gz", hash = "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1", size = 23012 } +sdist = { url = "https://files.pythonhosted.org/packages/00/7d/eb174f74e3f5634eaacb38031bbe467dfe2e545bc255e5c90096ec46bc46/types_requests-2.32.0.20250328.tar.gz", hash = "sha256:c9e67228ea103bd811c96984fac36ed2ae8da87a36a633964a21f199d60baf32", size = 22995 } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/26/645d89f56004aa0ba3b96fec27793e3c7e62b40982ee069e52568922b6db/types_requests-2.32.0.20250306-py3-none-any.whl", hash = "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b", size = 20673 }, + { url = "https://files.pythonhosted.org/packages/cc/15/3700282a9d4ea3b37044264d3e4d1b1f0095a4ebf860a99914fd544e3be3/types_requests-2.32.0.20250328-py3-none-any.whl", hash = "sha256:72ff80f84b15eb3aa7a8e2625fffb6a93f2ad5a0c20215fc1dcfa61117bcb2a2", size = 20663 }, ] [[package]] @@ -1159,32 +1576,80 @@ wheels = [ [[package]] name = "types-tqdm" -version = "4.67.0.20250301" +version = "4.67.0.20250417" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/e5/f4b821e6685702587a48af181476955384fc69262fc04f2d74da5602b111/types_tqdm-4.67.0.20250301.tar.gz", hash = "sha256:5e89a38ad89b867823368eb97d9f90d2fc69806bb055dde62716a05da62b5e0d", size = 17071 } +sdist = { url = "https://files.pythonhosted.org/packages/4a/54/1ce092a682af4b2995a3708a8830dc15502927f3586064d9ea3738a562d1/types_tqdm-4.67.0.20250417.tar.gz", hash = "sha256:bfcc4099d8d48df54e53f3ea64708cbcc1d1c4039ca7619594189da8c03c7be2", size = 17179 } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/47/1132d6c02e3bf1a66b7b1a57b8a62e0120f24b31fb770e0f9f3168ccdbfe/types_tqdm-4.67.0.20250301-py3-none-any.whl", hash = "sha256:8af97deb8e6874af833555dc1fe0fcd456b1a789470bf6cd8813d4e7ee4f6c5b", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/18/48/320971ed192f4ac207305a75fa8366efb1b6a90a24db0513f492668d87c0/types_tqdm-4.67.0.20250417-py3-none-any.whl", hash = "sha256:d43fc9a295be1f94083c744a09099c033c4dea293ff9a07bab9f34bfbffaaf80", size = 24057 }, ] [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, ] [[package]] name = "urllib3" -version = "2.3.0" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, +] + +[[package]] +name = "verspec" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/44/8126f9f0c44319b2efc65feaad589cadef4d77ece200ae3c9133d58464d0/verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e", size = 27123 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31", size = 19640 }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, ] [[package]] @@ -1208,6 +1673,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, ] +[[package]] +name = "zipp" +version = "3.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, +] + [[package]] name = "zstandard" version = "0.23.0"