Skip to content

feat: allow object store registration from datafusion-cli - #3540

Merged
tustvold merged 1 commit into
apache:masterfrom
turbo1912:cli-object-store
Sep 23, 2022
Merged

feat: allow object store registration from datafusion-cli#3540
tustvold merged 1 commit into
apache:masterfrom
turbo1912:cli-object-store

Conversation

@turbo1912

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Closes #3424.

Rationale for this change

This change provides an easy way for datafusion cli users to register object stores.

(inspiration from https://github.com/datafusion-contrib/datafusion-cookbook)

What changes are included in this PR?

Added environment variable support for registering s3 and gcp object stores. Currently users can only specify bucket name from the CLI and the rest of the configuration is setup from environment variables.

Are there any user-facing changes?

Datafusion cli users can now register an object store when they are starting the datafusion cli

datafusion cli --object-store s3 --bucket-name name

and then create external tables from object stores in the interactive shell:

create external table unicorns stored as parquet location 's3://my_bucket/lineitem/'
@turbo1912
turbo1912 force-pushed the cli-object-store branch 2 times, most recently from 873019b to 2df56c3 Compare September 20, 2022 01:09
Comment thread datafusion-cli/src/main.rs Outdated
Comment on lines +85 to +91
#[clap(
short = 'o',
long = "object-store",
help = "Register an object-store to Datafusion's execution context",
requires = "bucket-name"
)]
object_store_option: Option<ObjectStoreRegistrationOptions>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this param be repetitive?

Comment thread datafusion-cli/src/main.rs Outdated
);
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should abort with error if only one of two is supplied

Comment thread datafusion-cli/src/main.rs Outdated
Comment on lines +185 to +199
if let Ok(region) = env::var("AWS_REGION") {
builder = builder.with_region(region);
}

if let Ok(access_key) = env::var("AWS_ACCESS_KEY_ID") {
builder = builder.with_access_key_id(access_key);
}

if let Ok(key) = env::var("AWS_SECRET_ACCESS_KEY") {
builder = builder.with_secret_access_key(key);
}

if let Ok(token) = env::var("AWS_SESSION_TOKEN") {
builder = builder.with_token(token);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clap supports getting env var into param, see here

@alamb

alamb commented Sep 20, 2022

Copy link
Copy Markdown
Contributor

This PR is super exciting! THank you @turbo1912 and @jimexist

@kmitchener

Copy link
Copy Markdown
Contributor

I think that rather than any options, the support for object_store should be invisible to the user. So just running this SQL from datafusion-cli should be enough to trigger the cli to register the s3 objectstore:

create external table unicorns stored as parquet location 's3://my_bucket/lineitem/';

You could do that by creating the logical plan from the user's given SQL, then only registering an object store if they're creating an external location that's in the cloud. Something like:

    let logical_plan = ctx.create_logical_plan(sql)?;
    match logical_plan {
        LogicalPlan::CreateExternalTable(external) => {
            if external.location.to_lowercase().starts_with("s3://") {
                // use https://docs.rs/object_store/latest/object_store/path/struct.Path.html
                // parse the path, find the bucket
                // register the object store 
            }
        }
        _ => {},
    }
    // continue on and execute the logical plan
Comment thread datafusion-cli/src/main.rs Outdated
Ok(())
}

fn build_s3_object_store_from_env(bucket_name: &str) -> Result<AmazonS3> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh haha didn't realize this existed. Thanks!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only exists as of object_store v0.5.0 (cookbook still targets v0.4.0).

Should update the recipes to the latest version and leverage their features..

@turbo1912

Copy link
Copy Markdown
Contributor Author

I think that rather than any options, the support for object_store should be invisible to the user. So just running this SQL from datafusion-cli should be enough to trigger the cli to register the s3 objectstore:

create external table unicorns stored as parquet location 's3://my_bucket/lineitem/';

You could do that by creating the logical plan from the user's given SQL, then only registering an object store if they're creating an external location that's in the cloud. Something like:

    let logical_plan = ctx.create_logical_plan(sql)?;
    match logical_plan {
        LogicalPlan::CreateExternalTable(external) => {
            if external.location.to_lowercase().starts_with("s3://") {
                // use https://docs.rs/object_store/latest/object_store/path/struct.Path.html
                // parse the path, find the bucket
                // register the object store 
            }
        }
        _ => {},
    }
    // continue on and execute the logical plan

Yes @kmitchener! I thought of this as well, but then wasn't sure if every object-store type can be inferred from the url so took the less risky approach, that doesn't close any doors for the future. For example, can file://localhost mean multiple things?

That being said, looks like there is some object-store code already that makes some assumptions in regards to the url.

I am happy to go with this approach if there is interest! @alamb, @tustvold and @jimexist any thoughts?

@tustvold

tustvold commented Sep 20, 2022

Copy link
Copy Markdown
Contributor

I think it would be awesome to support this out of the box, and ObjectStoreRegistry should be able to handle this, after all a non-trivial amount of effort went into the design of ObjectStoreUrl to make sure this works. In particular I think it should just be a case of providing an ObjectStoreProvider that can create the various different schema.

apache/arrow-rs#2304 may also be relevant, FYI @roeap

@roeap

roeap commented Sep 20, 2022

Copy link
Copy Markdown
Contributor

In delta-rs I have been playing around with some of this, in the hopes to eventually get to a point to move this upstream into object store. Specifically, I took the OjectStoreUrl and gave it some "awareness" of what service it is referencing (s3 etc ..). (https://github.com/delta-io/delta-rs/blob/0fdaeed734b036d2bd7734eb3c4b3e56680dffff/rust/src/builder/mod.rs#L281-L407)

Also, I added parsing for some well known uris - especially azure has many different variants :) (https://github.com/delta-io/delta-rs/blob/0fdaeed734b036d2bd7734eb3c4b3e56680dffff/rust/src/builder/mod.rs#L428-L537)

Last but not least, we had several users who wanted to manage connections to several stores, so we added some logic to process property bags with several commonly used aliases for some of the config keys. and making sure passed config always takes precedence over config taken from the environment. (https://github.com/delta-io/delta-rs/blob/main/rust/src/builder/azure.rs)

Not sure how much of this - if anything :) - might be useful here or is "worthy" or moving upstream :).

@turbo1912
turbo1912 force-pushed the cli-object-store branch 2 times, most recently from dc19f1c to 42e1982 Compare September 22, 2022 06:24
Comment thread datafusion-cli/src/object_storage.rs Outdated
Comment on lines +56 to +59
match AmazonS3Builder::from_env().with_bucket_name(host).build() {
Ok(s3) => Some(Arc::new(s3)),
Err(_) => None,
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally I would like to write this as:

AmazonS3Builder::from_env()
    .with_bucket_name(host).build()
    .map(Arc::new)
    .ok();

but getting the following type error:

mismatched types
expected enum `Option<Arc<(dyn object_store::ObjectStore + 'static)>>`
found enum `Option<Arc<AmazonS3>>`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could write

Some(AmazonS3Builder::from_env() .with_bucket_name(host).build() .map(Arc::new).ok()?)

But it is a bit derived, and I think what you have written is better. The problem is that whilst Arc<AmazonS3> can be coerced to Arc<dyn ObjectStore>, Option<Arc<AmazonS3>> can't be coerced to Option<Arc<dyn ObjectStore>>

@turbo1912

Copy link
Copy Markdown
Contributor Author

ObjectStoreProvider works great, thanks for the suggestion @tustvold! I gave it a try, one thing that I am a little worried about is that there is no way for ObjectStoreProvider to communicate errors, for example if the url for a s3 object store is malformed provider would just return None and the ObjectStoreRegistry would silently not find the suitable object store.

@tustvold

Copy link
Copy Markdown
Contributor

I don't think there would be any objections to changing ObjectStoreProvider to return an error, after all if it returns None it just gets converted to an error anyway by ObjectStoreRegistry::get_by_url. I'll get a quick PR up to do this

@tustvold tustvold left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love it, happy to see these things coming together. I've created #3584 to address the error handling. Other than that and some very minor nits, this looks good to go 👍

Comment thread datafusion-cli/src/object_storage.rs Outdated
Comment thread datafusion-cli/src/object_storage.rs Outdated
Comment thread datafusion-cli/src/object_storage.rs Outdated
Comment on lines +56 to +59
match AmazonS3Builder::from_env().with_bucket_name(host).build() {
Ok(s3) => Some(Arc::new(s3)),
Err(_) => None,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could write

Some(AmazonS3Builder::from_env() .with_bucket_name(host).build() .map(Arc::new).ok()?)

But it is a bit derived, and I think what you have written is better. The problem is that whilst Arc<AmazonS3> can be coerced to Arc<dyn ObjectStore>, Option<Arc<AmazonS3>> can't be coerced to Option<Arc<dyn ObjectStore>>

@tustvold tustvold left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just needs clippy to be pacified and then this can go in 👍

@tustvold
tustvold merged commit b625277 into apache:master Sep 23, 2022
@tustvold

Copy link
Copy Markdown
Contributor

Thank you for this, this is really cool functionality to have out of the box

@ursabot

ursabot commented Sep 23, 2022

Copy link
Copy Markdown

Benchmark runs are scheduled for baseline = 49b9c67 and contender = b625277. b625277 is a master commit associated with this PR. Results will be available as each benchmark for each run completes.
Conbench compare runs links:
[Skipped ⚠️ Benchmarking of arrow-datafusion-commits is not supported on ec2-t3-xlarge-us-east-2] ec2-t3-xlarge-us-east-2
[Skipped ⚠️ Benchmarking of arrow-datafusion-commits is not supported on test-mac-arm] test-mac-arm
[Skipped ⚠️ Benchmarking of arrow-datafusion-commits is not supported on ursa-i9-9960x] ursa-i9-9960x
[Skipped ⚠️ Benchmarking of arrow-datafusion-commits is not supported on ursa-thinkcentre-m75q] ursa-thinkcentre-m75q
Buildkite builds:
Supported benchmarks:
ec2-t3-xlarge-us-east-2: Supported benchmark langs: Python, R. Runs only benchmarks with cloud = True
test-mac-arm: Supported benchmark langs: C++, Python, R
ursa-i9-9960x: Supported benchmark langs: Python, R, JavaScript
ursa-thinkcentre-m75q: Supported benchmark langs: C++, Java

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really neat -- thank you @turbo1912

@timvw

timvw commented Sep 24, 2022

Copy link
Copy Markdown
Contributor

+1. Thanks, really nice!

lyne7-sc pushed a commit to lyne7-sc/datafusion that referenced this pull request Aug 25, 2026
…oup (apache#24650)

Bumps the all-uv-deps group with 1 update:
[pygithub](https://github.com/pygithub/pygithub).

Updates `pygithub` from 2.9.1 to 2.10.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pygithub/pygithub/releases">pygithub's
releases</a>.</em></p>
<blockquote>
<h2>2.10.0</h2>
<h3>Notable changes</h3>
<h4>Drop Python 3.9 support due to End-of-Life</h4>
<p>Python 3.9 reached its end-of-life October 31, 2025. Support has been
removed with this release.</p>
<h3>New Features</h3>
<ul>
<li>Allow to specify the Github API version by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3480">PyGithub/PyGithub#3480</a></li>
<li>Add issue dependency endpoints by <a
href="https://github.com/james-geiger"><code>@​james-geiger</code></a>
in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3477">PyGithub/PyGithub#3477</a></li>
</ul>
<h3>Improvements</h3>
<ul>
<li>Add <code>incomplete_results</code> property to
<code>PaginatedList</code> by <a
href="https://github.com/Felixoid"><code>@​Felixoid</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3396">PyGithub/PyGithub#3396</a></li>
<li>Add <code>WorkflowRun.get_attempt</code> and
<code>Repository.get_workflow_job</code> functions by <a
href="https://github.com/cecheta"><code>@​cecheta</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3401">PyGithub/PyGithub#3401</a></li>
<li>Add support for <code>head_repo</code> when creating pull request by
<a href="https://github.com/shannon"><code>@​shannon</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3479">PyGithub/PyGithub#3479</a></li>
<li>Add <code>max_rate_limit_wait</code> to <code>GithubRetry</code> to
cap rate limit backoff by <a
href="https://github.com/tanistheta"><code>@​tanistheta</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3540">PyGithub/PyGithub#3540</a></li>
<li>Add <code>return_run_details</code> parameter to
<code>Workflow.create_dispatch</code> by <a
href="https://github.com/SebastienSyd"><code>@​SebastienSyd</code></a>
in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3471">PyGithub/PyGithub#3471</a></li>
<li>Update <code>SecurityAndAnalysis</code> by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3503">PyGithub/PyGithub#3503</a></li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>Fix duplicated URL in <code>OrganizationVariable.edit()</code> and
<code>OrganizationSecret.edit()</code> by <a
href="https://github.com/Krishnachaitanyakc"><code>@​Krishnachaitanyakc</code></a>
in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3472">PyGithub/PyGithub#3472</a></li>
<li>Fix <code>GithubException.__str__</code> decoding bytes data by <a
href="https://github.com/SAY-5"><code>@​SAY-5</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3494">PyGithub/PyGithub#3494</a></li>
<li>Fixing <code>OrganizationVariable.value</code> by properly using
base class by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3507">PyGithub/PyGithub#3507</a></li>
<li>Parse only the query string for
<code>PaginatedList.totalCount</code> by <a
href="https://github.com/Noethix55555"><code>@​Noethix55555</code></a>
in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3518">PyGithub/PyGithub#3518</a></li>
<li>Encode boolean query parameters as lowercase by <a
href="https://github.com/Noethix55555"><code>@​Noethix55555</code></a>
in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3516">PyGithub/PyGithub#3516</a></li>
<li>Fix doubled slash in request path for <code>base_url</code> with
trailing slash by <a
href="https://github.com/Noethix55555"><code>@​Noethix55555</code></a>
in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3537">PyGithub/PyGithub#3537</a></li>
<li>Fix <code>WorkflowRun</code> schema suggestion by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3559">PyGithub/PyGithub#3559</a></li>
<li>Fix OAuth2 authorization <code>state</code> /
<code>code_verifier</code> parameters by <a
href="https://github.com/TheoGoudout"><code>@​TheoGoudout</code></a> in
<a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3440">PyGithub/PyGithub#3440</a></li>
<li>Allow creating custom property with <code>values_editable_by</code>
param by <a
href="https://github.com/chawlajay9"><code>@​chawlajay9</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3548">PyGithub/PyGithub#3548</a></li>
<li>Fix <code>OrganizationSecret.edit()</code> to seal the value and use
<code>PUT</code> by <a
href="https://github.com/Noethix55555"><code>@​Noethix55555</code></a>
in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3529">PyGithub/PyGithub#3529</a></li>
<li>Fix lazy retrieval of latest tag name by <a
href="https://github.com/sathieu"><code>@​sathieu</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3487">PyGithub/PyGithub#3487</a></li>
<li>Fix typing and typos by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3485">PyGithub/PyGithub#3485</a></li>
</ul>
<h3>Maintenance</h3>
<ul>
<li>Add support for Python 3.15 and drop EOL 3.9 by <a
href="https://github.com/hugovk"><code>@​hugovk</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3551">PyGithub/PyGithub#3551</a></li>
<li>Move self-link awareness into <code>CompletableGithubObject</code>
by <a href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in
<a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3432">PyGithub/PyGithub#3432</a></li>
<li>Add documentation for agents, add Claude skills by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3509">PyGithub/PyGithub#3509</a></li>
<li>Add <code>sphinx-copybutton</code> for code blocks by <a
href="https://github.com/DivyanshiGautam31"><code>@​DivyanshiGautam31</code></a>
in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3493">PyGithub/PyGithub#3493</a></li>
<li>Add support to OpenAPI script for applying schema to methods by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3374">PyGithub/PyGithub#3374</a></li>
<li>Improve OpenAPI apply properties and methods by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3495">PyGithub/PyGithub#3495</a></li>
<li>Fix OpenAPI method verb detection by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3496">PyGithub/PyGithub#3496</a></li>
<li>Add OpenAPI script tests by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3489">PyGithub/PyGithub#3489</a></li>
<li>Fix OpenAPI apply method by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3498">PyGithub/PyGithub#3498</a></li>
<li>Reordering OpenAPI test sequence by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3500">PyGithub/PyGithub#3500</a></li>
<li>Apply typing convention by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3504">PyGithub/PyGithub#3504</a></li>
<li>Bump <code>mypy</code> Python version to 3.12 by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3505">PyGithub/PyGithub#3505</a></li>
<li>Use <code>@overload</code> to type <code>get_user</code> by <a
href="https://github.com/xmo-odoo"><code>@​xmo-odoo</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3502">PyGithub/PyGithub#3502</a></li>
<li>Add overload type annotations by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3506">PyGithub/PyGithub#3506</a></li>
<li>Simplify <code>OrganizationVariable</code> by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3508">PyGithub/PyGithub#3508</a></li>
<li>Add OpenAPI script filename option to create class by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3512">PyGithub/PyGithub#3512</a></li>
<li>OpenAPI script improvements by <a
href="https://github.com/EnricoMi"><code>@​EnricoMi</code></a> in <a
href="https://redirect.github.com/PyGithub/PyGithub/pull/3511">PyGithub/PyGithub#3511</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/PyGithub/PyGithub/blob/main/doc/changes.rst">pygithub's
changelog</a>.</em></p>
<blockquote>
<h2>Version 2.10.0 (August 20, 2026)</h2>
<p>Notable changes
^^^^^^^^^^^^^^^</p>
<p>Drop Python 3.9 support due to End-of-Life

&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;
Python 3.9 reached its end-of-life October 31, 2025. Support has been
removed with this release.</p>
<p>New Features
^^^^^^^^^^^^</p>
<ul>
<li>Allow to specify the Github API version
(<code>[apache#3480](PyGithub/PyGithub#3480)
&lt;https://github.com/PyGithub/PyGithub/pull/3480&gt;</code><em>)
(<code>546b0ee7e
&lt;https://github.com/PyGithub/PyGithub/commit/546b0ee7e&gt;</code></em>)</li>
<li>Add issue dependency endpoints
(<code>[apache#3477](PyGithub/PyGithub#3477)
&lt;https://github.com/PyGithub/PyGithub/pull/3477&gt;</code><em>)
(<code>b0ee6e0c7
&lt;https://github.com/PyGithub/PyGithub/commit/b0ee6e0c7&gt;</code></em>)</li>
</ul>
<p>Improvements
^^^^^^^^^^^^</p>
<ul>
<li>Add <code>incomplete_results</code> property to
<code>PaginatedList</code>
(<code>[apache#3396](PyGithub/PyGithub#3396)
&lt;https://github.com/PyGithub/PyGithub/pull/3396&gt;</code><em>)
(<code>7806e7dcc
&lt;https://github.com/PyGithub/PyGithub/commit/7806e7dcc&gt;</code></em>)</li>
<li>Add <code>WorkflowRun.get_attempt</code> and
<code>Repository.get_workflow_job</code> functions
(<code>[apache#3401](PyGithub/PyGithub#3401)
&lt;https://github.com/PyGithub/PyGithub/pull/3401&gt;</code><em>)
(<code>fdf9ed869
&lt;https://github.com/PyGithub/PyGithub/commit/fdf9ed869&gt;</code></em>)</li>
<li>Add support for <code>head_repo</code> when creating pull request
(<code>[apache#3479](PyGithub/PyGithub#3479)
&lt;https://github.com/PyGithub/PyGithub/pull/3479&gt;</code><em>)
(<code>8ddfd6b11
&lt;https://github.com/PyGithub/PyGithub/commit/8ddfd6b11&gt;</code></em>)</li>
<li>Add <code>max_rate_limit_wait</code> to <code>GithubRetry</code> to
cap rate limit backoff
(<code>[apache#3540](PyGithub/PyGithub#3540)
&lt;https://github.com/PyGithub/PyGithub/pull/3540&gt;</code><em>)
(<code>8b50a098f
&lt;https://github.com/PyGithub/PyGithub/commit/8b50a098f&gt;</code></em>)</li>
<li>Add <code>return_run_details</code> parameter to
<code>Workflow.create_dispatch</code>
(<code>[apache#3471](PyGithub/PyGithub#3471)
&lt;https://github.com/PyGithub/PyGithub/pull/3471&gt;</code><em>)
(<code>162ce9d0e
&lt;https://github.com/PyGithub/PyGithub/commit/162ce9d0e&gt;</code></em>)</li>
<li>Update <code>SecurityAndAnalysis</code>
(<code>[apache#3503](PyGithub/PyGithub#3503)
&lt;https://github.com/PyGithub/PyGithub/pull/3503&gt;</code><em>)
(<code>9674c5799
&lt;https://github.com/PyGithub/PyGithub/commit/9674c5799&gt;</code></em>)</li>
</ul>
<p>Bug Fixes
^^^^^^^^^</p>
<ul>
<li>Fix duplicated URL in <code>OrganizationVariable.edit()</code> and
<code>OrganizationSecret.edit()</code>
(<code>[apache#3472](PyGithub/PyGithub#3472)
&lt;https://github.com/PyGithub/PyGithub/pull/3472&gt;</code><em>)
(<code>a2e76eb8e
&lt;https://github.com/PyGithub/PyGithub/commit/a2e76eb8e&gt;</code></em>)</li>
<li>Fix <code>GithubException.__str__</code> decoding bytes data
(<code>[apache#3494](PyGithub/PyGithub#3494)
&lt;https://github.com/PyGithub/PyGithub/pull/3494&gt;</code><em>)
(<code>7a3b22ed6
&lt;https://github.com/PyGithub/PyGithub/commit/7a3b22ed6&gt;</code></em>)</li>
<li>Fixing <code>OrganizationVariable.value</code> by properly using
base class
(<code>[apache#3507](PyGithub/PyGithub#3507)
&lt;https://github.com/PyGithub/PyGithub/pull/3507&gt;</code><em>)
(<code>39a7464ff
&lt;https://github.com/PyGithub/PyGithub/commit/39a7464ff&gt;</code></em>)</li>
<li>Parse only the query string for
<code>PaginatedList.totalCount</code>
(<code>[apache#3518](PyGithub/PyGithub#3518)
&lt;https://github.com/PyGithub/PyGithub/pull/3518&gt;</code><em>)
(<code>645d2a2e8
&lt;https://github.com/PyGithub/PyGithub/commit/645d2a2e8&gt;</code></em>)</li>
<li>Encode boolean query parameters as lowercase
(<code>[apache#3516](PyGithub/PyGithub#3516)
&lt;https://github.com/PyGithub/PyGithub/pull/3516&gt;</code><em>)
(<code>f4e29299e
&lt;https://github.com/PyGithub/PyGithub/commit/f4e29299e&gt;</code></em>)</li>
<li>Fix doubled slash in request path for <code>base_url</code> with
trailing slash
(<code>[apache#3537](PyGithub/PyGithub#3537)
&lt;https://github.com/PyGithub/PyGithub/pull/3537&gt;</code><em>)
(<code>c15572f23
&lt;https://github.com/PyGithub/PyGithub/commit/c15572f23&gt;</code></em>)</li>
<li>Fix <code>WorkflowRun</code> schema suggestion
(<code>[apache#3559](PyGithub/PyGithub#3559)
&lt;https://github.com/PyGithub/PyGithub/pull/3559&gt;</code><em>)
(<code>5f707d64b
&lt;https://github.com/PyGithub/PyGithub/commit/5f707d64b&gt;</code></em>)</li>
<li>Fix OAuth2 authorization <code>state</code> /
<code>code_verifier</code> parameters
(<code>[apache#3440](PyGithub/PyGithub#3440)
&lt;https://github.com/PyGithub/PyGithub/pull/3440&gt;</code><em>)
(<code>4785224bc
&lt;https://github.com/PyGithub/PyGithub/commit/4785224bc&gt;</code></em>)</li>
<li>Allow creating custom property with <code>values_editable_by</code>
param (<code>[apache#3548](PyGithub/PyGithub#3548)
&lt;https://github.com/PyGithub/PyGithub/pull/3548&gt;</code><em>)
(<code>134f38a63
&lt;https://github.com/PyGithub/PyGithub/commit/134f38a63&gt;</code></em>)</li>
<li>Fix <code>OrganizationSecret.edit()</code> to seal the value and use
<code>PUT</code>
(<code>[apache#3529](PyGithub/PyGithub#3529)
&lt;https://github.com/PyGithub/PyGithub/pull/3529&gt;</code><em>)
(<code>4f8674b72
&lt;https://github.com/PyGithub/PyGithub/commit/4f8674b72&gt;</code></em>)</li>
<li>Fix lazy retrieval of latest tag name
(<code>[apache#3487](PyGithub/PyGithub#3487)
&lt;https://github.com/PyGithub/PyGithub/pull/3487&gt;</code><em>)
(<code>33071b35d
&lt;https://github.com/PyGithub/PyGithub/commit/33071b35d&gt;</code></em>)</li>
<li>Fix typing and typos
(<code>[apache#3485](PyGithub/PyGithub#3485)
&lt;https://github.com/PyGithub/PyGithub/pull/3485&gt;</code><em>)
(<code>fd7abf6a7
&lt;https://github.com/PyGithub/PyGithub/commit/fd7abf6a7&gt;</code></em>)</li>
</ul>
<p>Maintenance
^^^^^^^^^^^</p>
<ul>
<li>Add support for Python 3.15 and drop EOL 3.9
(<code>[apache#3551](PyGithub/PyGithub#3551)
&lt;https://github.com/PyGithub/PyGithub/pull/3551&gt;</code><em>)
(<code>5e2de3144
&lt;https://github.com/PyGithub/PyGithub/commit/5e2de3144&gt;</code></em>)</li>
<li>Move self-link awareness into <code>CompletableGithubObject</code>
(<code>[apache#3432](PyGithub/PyGithub#3432)
&lt;https://github.com/PyGithub/PyGithub/pull/3432&gt;</code><em>)
(<code>92ec3fab0
&lt;https://github.com/PyGithub/PyGithub/commit/92ec3fab0&gt;</code></em>)</li>
<li>Add documentation for agents, add Claude skills
(<code>[apache#3509](PyGithub/PyGithub#3509)
&lt;https://github.com/PyGithub/PyGithub/pull/3509&gt;</code><em>)
(<code>28f140d10
&lt;https://github.com/PyGithub/PyGithub/commit/28f140d10&gt;</code></em>)</li>
<li>Add <code>sphinx-copybutton</code> for code blocks
(<code>[apache#3493](PyGithub/PyGithub#3493)
&lt;https://github.com/PyGithub/PyGithub/pull/3493&gt;</code>_)</li>
<li>Add support to OpenAPI script for applying schema to methods
(<code>[apache#3374](PyGithub/PyGithub#3374)
&lt;https://github.com/PyGithub/PyGithub/pull/3374&gt;</code><em>)
(<code>b7a6d2324
&lt;https://github.com/PyGithub/PyGithub/commit/b7a6d2324&gt;</code></em>)</li>
<li>Improve OpenAPI apply properties and methods
(<code>[apache#3495](PyGithub/PyGithub#3495)
&lt;https://github.com/PyGithub/PyGithub/pull/3495&gt;</code>_)</li>
<li>Fix OpenAPI method verb detection
(<code>[apache#3496](PyGithub/PyGithub#3496)
&lt;https://github.com/PyGithub/PyGithub/pull/3496&gt;</code>_)</li>
<li>Add OpenAPI script tests
(<code>[apache#3489](PyGithub/PyGithub#3489)
&lt;https://github.com/PyGithub/PyGithub/pull/3489&gt;</code><em>)
(<code>09242d9b0
&lt;https://github.com/PyGithub/PyGithub/commit/09242d9b0&gt;</code></em>)</li>
<li>Fix OpenAPI apply method
(<code>[apache#3498](PyGithub/PyGithub#3498)
&lt;https://github.com/PyGithub/PyGithub/pull/3498&gt;</code><em>)
(<code>8f8697235
&lt;https://github.com/PyGithub/PyGithub/commit/8f8697235&gt;</code></em>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/PyGithub/PyGithub/commit/5cdf6ab395c52efb652d0bc1f28fdf2e452b1b41"><code>5cdf6ab</code></a>
Release 2.10.0 (<a
href="https://redirect.github.com/pygithub/pygithub/issues/3560">#3560</a>)</li>
<li><a
href="https://github.com/PyGithub/PyGithub/commit/33071b35d01033f5c059c7db02806710a7819330"><code>33071b3</code></a>
Fix lazy retrieval of latest tag name (<a
href="https://redirect.github.com/pygithub/pygithub/issues/3487">#3487</a>)</li>
<li><a
href="https://github.com/PyGithub/PyGithub/commit/4f8674b724f9c0bbeac36a78cbf5f7fc577b9bf2"><code>4f8674b</code></a>
Fix OrganizationSecret.edit() to seal the value and use PUT (<a
href="https://redirect.github.com/pygithub/pygithub/issues/3529">#3529</a>)</li>
<li><a
href="https://github.com/PyGithub/PyGithub/commit/c63d09e261b627b3f8ce6777038ed818a86d73b4"><code>c63d09e</code></a>
docs: add example for uploading multiple files in a single commit (<a
href="https://redirect.github.com/pygithub/pygithub/issues/3545">#3545</a>)</li>
<li><a
href="https://github.com/PyGithub/PyGithub/commit/4785224bc5089d0fd7933367d7f9fc0171d2d8d8"><code>4785224</code></a>
Fix OAuth2 authorization <code>state</code> / <code>code_verifier</code>
parameters (<a
href="https://redirect.github.com/pygithub/pygithub/issues/3440">#3440</a>)</li>
<li><a
href="https://github.com/PyGithub/PyGithub/commit/134f38a63dbcf0161ecd7df30e398f24f14e9fad"><code>134f38a</code></a>
Allow creating custom property with values_editable_by param (<a
href="https://redirect.github.com/pygithub/pygithub/issues/3548">#3548</a>)</li>
<li><a
href="https://github.com/PyGithub/PyGithub/commit/5f707d64b86d63b66820ed3941785fa803476625"><code>5f707d6</code></a>
Fix <code>WorkflowRun</code> schema suggestion (<a
href="https://redirect.github.com/pygithub/pygithub/issues/3559">#3559</a>)</li>
<li><a
href="https://github.com/PyGithub/PyGithub/commit/82809291e065be02354a9673750a8ae35d363bdd"><code>8280929</code></a>
docs: add exception handling example (<a
href="https://redirect.github.com/pygithub/pygithub/issues/3543">#3543</a>)</li>
<li><a
href="https://github.com/PyGithub/PyGithub/commit/c15572f23aa4743efe4b1f88bd26ddfb0c46acf8"><code>c15572f</code></a>
Fix doubled slash in request path for <code>base_url</code> with
trailing slash (<a
href="https://redirect.github.com/pygithub/pygithub/issues/3537">#3537</a>)</li>
<li><a
href="https://github.com/PyGithub/PyGithub/commit/162ce9d0ed69b9e4950c92e1896ab3539a9cba1a"><code>162ce9d</code></a>
Add <code>return_run_details</code> parameter to
<code>Workflow.create_dispatch</code> (<a
href="https://redirect.github.com/pygithub/pygithub/issues/3471">#3471</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pygithub/pygithub/compare/v2.9.1...v2.10.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pygithub&package-manager=uv&previous-version=2.9.1&new-version=2.10.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

8 participants