@@ -708,33 +708,36 @@ static void InitializeConnectionMethods(nb::module_ &m) {
708708 " run the query as-is." ,
709709 nb::arg (" query" ), nb::kw_only (), nb::arg (" alias" ) = " " , nb::arg (" params" ) = nb::none (),
710710 nb::arg (" connection" ).none () = nb::none ());
711- m.def (
712- " read_csv" ,
713- // nb::arg + nb::kwargs can't coexist under nanobind's annotation rules; drop the annotations.
714- [](const nb::object &name, nb::kwargs &kwargs) {
715- std::shared_ptr<DuckDBPyConnection> conn;
716- if (kwargs.contains (" conn" ) && !kwargs[" conn" ].is_none ()) {
717- conn = nb::cast<std::shared_ptr<DuckDBPyConnection>>(kwargs[" conn" ]);
718- }
719- if (!conn) {
720- conn = DuckDBPyConnection::DefaultConnection ();
721- }
722- return conn->ReadCSV (name, kwargs);
723- },
724- " Create a relation object from the CSV file in 'name'" );
725- m.def (
726- " from_csv_auto" ,
727- [](const nb::object &name, nb::kwargs &kwargs) {
728- std::shared_ptr<DuckDBPyConnection> conn;
729- if (kwargs.contains (" conn" ) && !kwargs[" conn" ].is_none ()) {
730- conn = nb::cast<std::shared_ptr<DuckDBPyConnection>>(kwargs[" conn" ]);
731- }
732- if (!conn) {
733- conn = DuckDBPyConnection::DefaultConnection ();
734- }
735- return conn->ReadCSV (name, kwargs);
736- },
737- " Create a relation object from the CSV file in 'name'" );
711+ // nanobind's all-or-nothing nb::arg rule forbids naming just the source parameter alongside **kwargs, so the
712+ // module-level read_csv / from_csv_auto take (*args, **kwargs) and recover the advertised keywords by hand:
713+ // the source may be positional or passed as `path_or_buffer=`, and the connection as `connection=` / `conn=`.
714+ // Each recovered keyword is popped from kwargs so ReadCSV's unknown-parameter check only sees CSV options.
715+ // N2: extra positional args (e.g. read_csv("a", "b")) are silently dropped rather than raising; negligible.
716+ auto module_read_csv = [](nb::args args, nb::kwargs kwargs) {
717+ nb::object name = nb::none ();
718+ if (args.size () >= 1 ) {
719+ name = nb::object (args[0 ]);
720+ } else if (kwargs.contains (" path_or_buffer" )) {
721+ name = kwargs[" path_or_buffer" ];
722+ PyDict_DelItemString (kwargs.ptr (), " path_or_buffer" );
723+ }
724+ std::shared_ptr<DuckDBPyConnection> conn;
725+ for (const char *conn_key : {" connection" , " conn" }) {
726+ if (kwargs.contains (conn_key)) {
727+ nb::object conn_arg = kwargs[conn_key];
728+ PyDict_DelItemString (kwargs.ptr (), conn_key);
729+ if (!conn && !conn_arg.is_none ()) {
730+ conn = nb::cast<std::shared_ptr<DuckDBPyConnection>>(conn_arg);
731+ }
732+ }
733+ }
734+ if (!conn) {
735+ conn = DuckDBPyConnection::DefaultConnection ();
736+ }
737+ return conn->ReadCSV (name, kwargs);
738+ };
739+ m.def (" read_csv" , module_read_csv, " Create a relation object from the CSV file in 'name'" );
740+ m.def (" from_csv_auto" , module_read_csv, " Create a relation object from the CSV file in 'name'" );
738741 m.def (
739742 " from_df" ,
740743 [](const PandasDataFrame &value, std::shared_ptr<DuckDBPyConnection> conn = nullptr ) {
@@ -822,9 +825,21 @@ static void InitializeConnectionMethods(nb::module_ &m) {
822825 " Load an installed extension" , nb::arg (" extension" ), nb::kw_only (), nb::arg (" connection" ).none () = nb::none ());
823826 m.def (
824827 " project" ,
825- // nanobind forbids named typed parameters after nb::args; the keyword-only `groups` and `connection`
826- // are therefore taken from **kwargs (preserving the previous defaults/None-handling).
827- [](const PandasDataFrame &df, const nb::args &args, const nb::kwargs &kwargs) {
828+ // nanobind forbids named typed parameters after nb::args, so this takes (*args, **kwargs) and recovers the
829+ // advertised signature by hand: `df` may be positional (args[0]) or the `df=` keyword (the stubs advertise
830+ // it as positional-or-keyword); the remaining positionals are projection expressions; `groups` /
831+ // `connection` are keyword-only (pulled from kwargs, preserving the previous defaults/None-handling).
832+ [](const nb::args &args, const nb::kwargs &kwargs) {
833+ nb::object df_obj = nb::none ();
834+ nb::args proj_args = nb::steal<nb::args>(PyTuple_New (0 ));
835+ if (args.size () >= 1 ) {
836+ df_obj = nb::object (args[0 ]);
837+ proj_args = nb::steal<nb::args>(PyTuple_GetSlice (args.ptr (), 1 , static_cast <Py_ssize_t>(args.size ())));
838+ } else if (kwargs.contains (" df" )) {
839+ df_obj = kwargs[" df" ];
840+ PyDict_DelItemString (kwargs.ptr (), " df" );
841+ }
842+ auto df = nb::cast<PandasDataFrame>(df_obj);
828843 string groups = " " ;
829844 if (kwargs.contains (" groups" ) && !kwargs[" groups" ].is_none ()) {
830845 groups = nb::cast<std::string>(kwargs[" groups" ]);
@@ -836,7 +851,7 @@ static void InitializeConnectionMethods(nb::module_ &m) {
836851 if (!conn) {
837852 conn = DuckDBPyConnection::DefaultConnection ();
838853 }
839- return conn->FromDF (df)->Project (args , groups);
854+ return conn->FromDF (df)->Project (proj_args , groups);
840855 },
841856 " Project the relation object by the projection in project_expr" );
842857 m.def (
0 commit comments