From 2605e39b2e27296a5f624482ab4668760b529111 Mon Sep 17 00:00:00 2001 From: saint1991 Date: Wed, 25 Sep 2024 07:23:18 +0900 Subject: [PATCH 1/3] Returing result implementation to tonic server --- .gitignore | 4 +- .vscode/launch.json | 3 +- .vscode/settings.json | 8 ++ Cargo.lock | 59 ++++++--- Cargo.toml | 15 ++- build.rs | 2 +- client/.gitignore | 1 - client/client.py | 5 +- client/request.py | 44 +++++++ proto/database.proto | 97 ++++++++++++++ proto/error.proto | 13 ++ proto/location.proto | 14 +++ proto/message.proto | 35 ------ proto/query.proto | 44 +++++++ proto/service.proto | 32 ++++- src/error.rs | 64 ++++++++++ src/gduck.rs | 141 +++++++++++++++++++++ src/main.rs | 4 + src/proto.rs | 287 ++++++++++++++++++++++++++++++++++++++++++ src/service.rs | 83 ++++++------ src/uri.rs | 19 +++ 21 files changed, 864 insertions(+), 110 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 client/request.py create mode 100644 proto/database.proto create mode 100644 proto/error.proto create mode 100644 proto/location.proto delete mode 100644 proto/message.proto create mode 100644 proto/query.proto create mode 100644 src/error.rs create mode 100644 src/gduck.rs create mode 100644 src/proto.rs create mode 100644 src/uri.rs diff --git a/.gitignore b/.gitignore index a3a3882..c06a61d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ target gen -*.duckdb \ No newline at end of file +*.duckdb +.env +datasets \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 30f44e7..e615ed5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -38,7 +38,8 @@ }, "args": [], "env": { - "RUST_LOG": "info" + "RUST_LOG": "info", + "RUST_BACKTRACE": "1" }, "cwd": "${workspaceFolder}" } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4c3bf6d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "protoc": { + "compile_on_save": false, + "options": [ + "--proto_path=${workspaceFolder}/proto" + ] + } +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 98241bb..de6aae8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,9 +306,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", @@ -545,7 +545,9 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", "windows-targets", ] @@ -615,6 +617,7 @@ checksum = "626373a331b49f94b24edc4e53a59b0b354f085ac3b339d43d31da7a9b145004" dependencies = [ "arrow", "cast", + "chrono", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -740,9 +743,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-sink" @@ -774,11 +777,14 @@ version = "0.1.0" dependencies = [ "anyhow", "async-stream", + "chrono", "duckdb", "env_logger", "futures-core", "log", "prost", + "prost-types", + "thiserror", "tokio", "tokio-stream", "tonic", @@ -1440,9 +1446,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", "prost-derive", @@ -1471,9 +1477,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", "itertools", @@ -1484,9 +1490,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" dependencies = [ "prost", ] @@ -1877,6 +1883,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -1971,9 +1997,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", @@ -2001,13 +2027,14 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4ee8877250136bd7e3d2331632810a4df4ea5e004656990d8d66d2f5ee8a67" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" dependencies = [ "prettyplease", "proc-macro2", "prost-build", + "prost-types", "quote", "syn 2.0.77", ] diff --git a/Cargo.toml b/Cargo.toml index 5e64ccc..33c7484 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,16 +5,19 @@ edition = "2021" [dependencies] anyhow = { version = "1.0.89", features = ["backtrace", "std"] } -async-stream = { version = "0.3.5" } -duckdb = { version = "1.0.0", features = ["bundled"] } +async-stream = { version = "0.3.6" } +chrono = { version = "0.4.38" } +duckdb = { version = "1.0.0", features = ["bundled", "chrono"] } env_logger = { version = "0.11.5" } -futures-core = { version = "0.3.30" } +futures-core = { version = "0.3.31" } log = { version = "0.4.22" } -prost = { version = "0.13.2" } +prost = { version = "0.13.3" } +prost-types = { version = "0.13.3" } +thiserror = "1.0.64" tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "sync" ] } tokio-stream = { version = "0.1.16" } -tonic = { version = "0.12.2" } +tonic = { version = "0.12.3" } [build-dependencies] -tonic-build = { version = "0.12.2" } +tonic-build = { version = "0.12.3" } diff --git a/build.rs b/build.rs index 7240e74..cee0e42 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,6 @@ fn main() -> Result<(), Box> { tonic_build::configure() .build_client(false) - .compile(&["proto/service.proto"], &["proto"])?; + .compile_protos(&["proto/service.proto"], &["proto"])?; Ok(()) } diff --git a/client/.gitignore b/client/.gitignore index 7662570..ab621de 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -1,3 +1,2 @@ -.env gen __pycache__ \ No newline at end of file diff --git a/client/client.py b/client/client.py index 37e405c..f83479b 100644 --- a/client/client.py +++ b/client/client.py @@ -8,11 +8,8 @@ import grpc from grpc._channel import _MultiThreadedRendezvous -from message_pb2 import Request from service_pb2_grpc import DbServiceStub -ConnectionMode = Literal["auto", "read_write", "read_only"] - @dataclass(frozen=True) class Addr: @@ -100,7 +97,7 @@ def mode(self) -> Request.Connect.Mode: elif self._mode == "read_write": return Request.Connect.Mode.MODE_READ_WRITE elif self._mode == "read_only": - return Request.Connect.Mode.MODE_READ_ONLY + return Request.Connect.Mode.MODE_READ_ONLY else: raise ValueError(f"Unknown mode: {self._mode}") diff --git a/client/request.py b/client/request.py new file mode 100644 index 0000000..2d8440f --- /dev/null +++ b/client/request.py @@ -0,0 +1,44 @@ +from datetime import date, datetime, time, timedelta +from decimal import Decimal +from typing import Literal, TypeAlias + +from database_pb2 import Connect, DataType +from database_pb2 import Decimal as ProtoDecimal +from database_pb2 import Params, Row, Rows, ScalarValue, Schema +from error_pb2 import Error, ErrorCode +from google.protobuf.struct_pb2 import NULL_VALUE +from location_pb2 import Location +from query_pb2 import Query + +__all__ = ["ConnectionMode", "connect"] + +ConnectionMode = Literal["auto", "read_write", "read_only"] +Value: TypeAlias = bool | int | float | Decimal | str | datetime | date | time | timedelta | None + + +def _mode(m: ConnectionMode = Connect.Mode.MODE_AUTO) -> Connect.Mode: + if m == "read_write": + return Connect.Mode.MODE_READ_WRITE + elif m == "read_only": + return Connect.Mode.MODE_READ_ONLY + else: + return Connect.Mode.MODE_AUTO + + +def connect(file_name: str, mode: ConnectionMode) -> Connect: + return Connect(file_name=file_name, mode=_mode(mode)) + + +def _value(v: Value) -> ScalarValue: + if v is None: + return ScalarValue(null_value=NULL_VALUE) + elif type(v) is bool: + return ScalarValue(bool_value=v) + elif type(v) is int: + return ScalarValue(int_value=v) + elif type(v) is float: + return ScalarValue(double_value=v) + elif type(v) is Decimal: + return ScalarValue(decimal_value=ProtoDecimal(value=str(v))) + elif type(v) is str: + return ScalarValue(str_value=v) diff --git a/proto/database.proto b/proto/database.proto new file mode 100644 index 0000000..439ca8f --- /dev/null +++ b/proto/database.proto @@ -0,0 +1,97 @@ +syntax = "proto3"; + +package gduck; + +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; + +message Connect { + enum Mode { + MODE_AUTO = 0; + MODE_READ_WRITE = 1; + MODE_READ_ONLY = 2; + } + + string file_name = 1; + Mode mode = 2; + } + +enum DataType { + DATATYPE_UNSPECIFIED = 0; + DATATYPE_NULL = 1; + DATATYPE_BOOL = 2; + DATATYPE_INT = 3; + DATATYPE_UINT = 4; + DATATYPE_DOUBLE = 5; + DATATYPE_DECIMAL= 6; + DATATYPE_STRING = 7; + DATATYPE_DATETIME = 8; + DATATYPE_DATE = 9; + DATATYPE_TIME = 10; + DATATYPE_INTERVAL = 11; +} + +message Column { + string name = 1; + DataType data_type = 2; +} + +message Schema { + repeated Column columns = 1; +} + +// refered to https://github.com/googleapis/googleapis/blob/master/google/type/decimal.proto +message Decimal { + string value = 1; +} + +// refered to https://github.com/googleapis/googleapis/blob/master/google/type/date.proto +message Date { + int32 year = 1; + uint32 month = 2; + uint32 day = 3; +} + +// refered to https://github.com/googleapis/googleapis/blob/master/google/type/timeofday.proto +message Time { + uint32 hours = 1; + uint32 minutes = 2; + uint32 seconds = 3; + uint32 nanos = 4; +} + +message Interval { + int32 months = 1; + int32 days = 2; + int64 nanos = 3; +} + +message ScalarValue { + oneof kind { + google.protobuf.NullValue null_value = 1; + bool bool_value = 2; + int64 int_value = 3; + uint64 uint_value = 4; + double double_value = 5; + Decimal decimal_value = 6; + string str_value = 7; + google.protobuf.Timestamp datetime_value = 8; + Date date_value = 9; + Time time_value = 10; + Interval interval_value = 11; + } +} + +message Params { + repeated ScalarValue params = 1; +} + +message Row { + repeated ScalarValue values = 1; +} + +message Rows { + Schema schema = 1; + repeated Row rows = 2; +} + diff --git a/proto/error.proto b/proto/error.proto new file mode 100644 index 0000000..73f2412 --- /dev/null +++ b/proto/error.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package gduck; + +enum ErrorCode { + ERROR_CODE_UNSPECIFIED = 0; + ERROR_CODE_INTERNAL = 1; +} + +message Error { + ErrorCode code = 1; + string message = 2; +} diff --git a/proto/location.proto b/proto/location.proto new file mode 100644 index 0000000..b8ea20f --- /dev/null +++ b/proto/location.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package gduck; + +message Location { + + message LocalFile { + string path = 1; + } + + oneof kind { + LocalFile local = 1; + } +} diff --git a/proto/message.proto b/proto/message.proto deleted file mode 100644 index 975d71b..0000000 --- a/proto/message.proto +++ /dev/null @@ -1,35 +0,0 @@ -syntax = "proto3"; - -package gduck; - -message Request { - - message Connect { - enum Mode { - MODE_AUTO = 0; - MODE_READ_WRITE = 1; - MODE_READ_ONLY = 2; - } - - string file_name = 1; - Mode mode = 2; - } - - message Query { - string query = 1; - } - - oneof message { - Connect connect = 1; - Query query = 2; - } -} - -message Response { - - message QueryResult { - string result = 1; - } - - QueryResult result = 1; -} diff --git a/proto/query.proto b/proto/query.proto new file mode 100644 index 0000000..3bed94d --- /dev/null +++ b/proto/query.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package gduck; + +import "database.proto"; +import "location.proto"; + +message Query { + + message Execute { + string query = 1; + Params params = 2; + } + + message QueryValue { + string query = 1; + Params params = 2; + } + + message QueryRows { + string query = 1; + Params params = 2; + } + + message CreateTableAsQuery { + string table_name = 1; + string query = 2; + Params params = 3; + } + + message ParquetQuery { + Location location = 1; + string query = 2; + Params params = 3; + } + + oneof kind { + Execute execute = 1; + QueryValue value = 2; + QueryRows rows = 3; + CreateTableAsQuery ctas = 4; + ParquetQuery parquet = 5; + } +} diff --git a/proto/service.proto b/proto/service.proto index e90d84c..a6da294 100644 --- a/proto/service.proto +++ b/proto/service.proto @@ -2,7 +2,37 @@ syntax = "proto3"; package gduck; -import "message.proto"; +import "google/protobuf/empty.proto"; + +import "database.proto"; +import "error.proto"; +import "location.proto"; +import "query.proto"; + +message Request { + oneof message { + Connect connect = 1; + Query query = 2; + } +} + +message Response { + + message QueryResult { + oneof kind { + google.protobuf.Empty ok = 1; + ScalarValue value = 2; + Rows rows = 3; + Location parquet_file = 4; + } + } + + oneof result { + QueryResult success = 1; + Error error = 2; + } +} + service DbService { rpc Transaction(stream Request) returns (stream Response) {}; diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..47c4e4c --- /dev/null +++ b/src/error.rs @@ -0,0 +1,64 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Protocol error: {message}.")] + ProtocolError { message: String }, + + #[error("Error occured at database: {message}.")] + DatabaseError { message: String }, + + #[error("Query error: {message}.")] + QueryError { message: String }, + + #[error("Unsupported type: {t}.")] + UnsupportedTypeError { t: duckdb::types::Type }, + + #[error("Unsupported parquet uri: {0}.")] + UnsupportedParquetUri(String), + + #[error("Invalid request: {0}.")] + InvalidRequest(String), + + #[error("Internal error: {message}.")] + InternalError { message: String }, +} + +impl Error { + pub const fn unsupported_type(t: duckdb::types::Type) -> Self { + Error::UnsupportedTypeError { t: t } + } + + pub fn internal>(message: S) -> Self { + Error::InternalError { + message: String::from(message.as_ref()), + } + } +} + +pub type Result = std::result::Result; + +impl From for Error { + fn from(value: duckdb::Error) -> Self { + Self::DatabaseError { + message: value.to_string(), + } + } +} + +impl From for Error { + fn from(value: duckdb::types::FromSqlError) -> Self { + match value { + duckdb::types::FromSqlError::InvalidType => Error::DatabaseError { + message: String::from("invalid type"), + }, + duckdb::types::FromSqlError::OutOfRange(range) => Error::DatabaseError { + message: format!("value out of range ({})", range), + }, + duckdb::types::FromSqlError::Other(err) => Error::DatabaseError { + message: err.to_string(), + }, + _ => Error::DatabaseError { + message: String::from("unknown"), + }, + } + } +} diff --git a/src/gduck.rs b/src/gduck.rs new file mode 100644 index 0000000..20f1e6b --- /dev/null +++ b/src/gduck.rs @@ -0,0 +1,141 @@ +use crate::error::{Error, Result}; +use crate::proto; +use crate::uri::Uri; + +pub struct Gduck { + conn: duckdb::Connection, +} + +impl Gduck { + pub fn new(conn: duckdb::Connection) -> Self { + Self { conn } + } + + pub fn connect(conn: proto::Connect) -> Result { + let conn = duckdb::Connection::open_with_flags( + std::path::PathBuf::from(&conn.file_name), + duckdb::Config::try_from(conn)?, + ) + .map_err(crate::error::Error::from)?; + Ok(Gduck::new(conn)) + } + + pub(crate) fn schema( + schema: std::sync::Arc, + ) -> Result { + let columns = schema + .fields() + .into_iter() + .map(|field| { + proto::DataType::try_from(field.data_type().to_owned()).map(|column_type| { + proto::Column { + name: field.name().to_owned(), + data_type: column_type as i32, + } + }) + }) + .collect::>>()?; + + Ok(proto::Schema { columns: columns }) + } + + pub fn execute>( + &self, + sql: Q, + params: proto::Params, + ) -> Result { + let params: duckdb::ParamsFromIter> = params.try_into()?; + self.conn + .execute(sql.as_ref(), params) + .map_err(Error::from)?; + Ok(proto::response::QueryResult { + kind: Some(proto::response::query_result::Kind::Ok(())), + }) + } + + pub fn query_value>( + &self, + sql: Q, + params: proto::Params, + ) -> Result { + let params: duckdb::ParamsFromIter> = params.try_into()?; + self.conn.query_row_and_then(sql.as_ref(), params, |row| { + let kind = row.get::(0)?; + Ok(proto::response::QueryResult { + kind: Some(proto::response::query_result::Kind::Value( + proto::ScalarValue { kind: Some(kind) }, + )), + }) + }) + } + + pub fn query_rows>( + &self, + sql: Q, + params: proto::Params, + ) -> Result { + let params: duckdb::ParamsFromIter> = params.try_into()?; + + let mut statement = self.conn.prepare(sql.as_ref())?; + + let rows = statement.query_and_then(params, |row| { + let mut values = prost::alloc::vec![]; + for i in 0.. { + match row.get::(i) { + Ok(kind) => { + values.push(proto::ScalarValue { kind: Some(kind) }); + } + Err(duckdb::Error::InvalidColumnIndex(_)) => { + break; + } + Err(err) => { + return Err(err); + } + } + } + Ok(proto::Row { values }) + }).and_then(|rows| { + rows.collect::, duckdb::Error>>() + }).map_err(crate::error::Error::from)?; + + let schema = Self::schema(statement.schema())?; + + Ok(proto::response::QueryResult { + kind: Some(proto::response::query_result::Kind::Rows(proto::Rows { + schema: Some(schema), + rows: rows, + })), + }) + } + + pub fn create_table_as, Q: AsRef>( + &self, + table: T, + sql: Q, + params: proto::Params, + ) -> Result { + let ctas_query = format!("CREATE TABLE {} AS {}", table.as_ref(), sql.as_ref()); + self.execute(ctas_query, params) + } + + pub fn query_as_parquet>( + &self, + sql: Q, + params: proto::Params, + uri: Uri, + ) -> Result { + let sql = sql.as_ref().trim(); + let query = format!( + "COPY ({}) TO '{}' (FORMAT PARQUET)", + sql.strip_suffix(";").unwrap_or_else(|| sql), + uri + ); + self.execute(query, params).and_then(|_| { + Ok(proto::response::QueryResult { + kind: Some(proto::response::query_result::Kind::ParquetFile( + proto::Location::try_from(uri)?, + )), + }) + }) + } +} diff --git a/src/main.rs b/src/main.rs index 18b0235..1d72d6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,8 @@ +mod error; +mod gduck; +mod proto; mod service; +mod uri; use std::net::SocketAddr; diff --git a/src/proto.rs b/src/proto.rs new file mode 100644 index 0000000..9e6bd6c --- /dev/null +++ b/src/proto.rs @@ -0,0 +1,287 @@ +use chrono::{Datelike, Timelike}; + +tonic::include_proto!("gduck"); + +impl From for duckdb::AccessMode { + fn from(value: connect::Mode) -> Self { + match value { + connect::Mode::Auto => duckdb::AccessMode::Automatic, + connect::Mode::ReadWrite => duckdb::AccessMode::ReadWrite, + connect::Mode::ReadOnly => duckdb::AccessMode::ReadOnly, + } + } +} + +impl TryFrom for duckdb::Config { + type Error = crate::error::Error; + + fn try_from(value: Connect) -> crate::error::Result { + duckdb::Config::default() + .access_mode(value.mode().into()) + .map_err(crate::error::Error::from) + } +} + +impl TryFrom for crate::uri::Uri { + type Error = crate::error::Error; + + fn try_from(value: Location) -> Result { + match value.kind { + Some(location::Kind::Local(local)) => Ok(crate::uri::Uri::LocalFileSystem( + std::path::PathBuf::from(local.path), + )), + None => Err(crate::error::Error::ProtocolError { + message: String::from("Parquet file location is required."), + }), + } + } +} + +impl TryFrom for Location { + type Error = crate::error::Error; + + fn try_from(uri: crate::uri::Uri) -> Result { + match uri { + crate::uri::Uri::LocalFileSystem(path) => Ok(Location { + kind: Some(location::Kind::Local(location::LocalFile { + path: String::from(path.to_string_lossy()), + })), + }), + _ => Err(crate::error::Error::UnsupportedParquetUri(uri.to_string())), + } + } +} + +impl TryFrom for DataType { + type Error = crate::error::Error; + + fn try_from(value: duckdb::types::Type) -> Result { + match value { + duckdb::types::Type::Null => Ok(DataType::DatatypeNull), + duckdb::types::Type::Boolean => Ok(DataType::DatatypeBool), + duckdb::types::Type::TinyInt + | duckdb::types::Type::SmallInt + | duckdb::types::Type::Int + | duckdb::types::Type::BigInt => Ok(DataType::DatatypeInt), + duckdb::types::Type::UTinyInt + | duckdb::types::Type::USmallInt + | duckdb::types::Type::UInt + | duckdb::types::Type::UBigInt => Ok(DataType::DatatypeUint), + duckdb::types::Type::Float | duckdb::types::Type::Double => { + Ok(DataType::DatatypeDouble) + } + duckdb::types::Type::Decimal => Ok(DataType::DatatypeDecimal), + duckdb::types::Type::Text => Ok(DataType::DatatypeString), + duckdb::types::Type::Timestamp => Ok(DataType::DatatypeDatetime), + duckdb::types::Type::Date32 => Ok(DataType::DatatypeDate), + duckdb::types::Type::Time64 => Ok(DataType::DatatypeTime), + duckdb::types::Type::Interval => Ok(DataType::DatatypeInterval), + other_type => Err(crate::error::Error::unsupported_type(other_type)), + } + } +} + +impl TryFrom for DataType { + type Error = crate::error::Error; + + fn try_from(value: duckdb::arrow::datatypes::DataType) -> Result { + match value { + duckdb::arrow::datatypes::DataType::Null => Ok(DataType::DatatypeNull), + duckdb::arrow::datatypes::DataType::Boolean => Ok(DataType::DatatypeBool), + duckdb::arrow::datatypes::DataType::Int8 + | duckdb::arrow::datatypes::DataType::Int16 + | duckdb::arrow::datatypes::DataType::Int32 + | duckdb::arrow::datatypes::DataType::Int64 => Ok(DataType::DatatypeInt), + duckdb::arrow::datatypes::DataType::UInt8 + | duckdb::arrow::datatypes::DataType::UInt16 + | duckdb::arrow::datatypes::DataType::UInt32 + | duckdb::arrow::datatypes::DataType::UInt64 => Ok(DataType::DatatypeUint), + duckdb::arrow::datatypes::DataType::Float16 + | duckdb::arrow::datatypes::DataType::Float32 + | duckdb::arrow::datatypes::DataType::Float64 => Ok(DataType::DatatypeDouble), + duckdb::arrow::datatypes::DataType::Decimal128(_, _) + | duckdb::arrow::datatypes::DataType::Decimal256(_, _) => Ok(DataType::DatatypeDecimal), + duckdb::arrow::datatypes::DataType::Utf8 + | duckdb::arrow::datatypes::DataType::Utf8View + | duckdb::arrow::datatypes::DataType::LargeUtf8 => Ok(DataType::DatatypeString), + duckdb::arrow::datatypes::DataType::Timestamp(_, _) => Ok(DataType::DatatypeDatetime), + duckdb::arrow::datatypes::DataType::Date32 => Ok(DataType::DatatypeDate), + duckdb::arrow::datatypes::DataType::Time64(_) => Ok(DataType::DatatypeTime), + duckdb::arrow::datatypes::DataType::Interval(_) => Ok(DataType::DatatypeInterval), + t => Err(crate::error::Error::UnsupportedTypeError { + t: duckdb::types::Type::from(&t), + }), + } + } +} + +impl duckdb::types::ToSql for scalar_value::Kind { + fn to_sql(&self) -> duckdb::Result> { + match self { + scalar_value::Kind::NullValue(_) => { + Ok(duckdb::types::ToSqlOutput::from(duckdb::types::Null)) + } + scalar_value::Kind::BoolValue(bool) => Ok(duckdb::types::ToSqlOutput::from(*bool)), + scalar_value::Kind::IntValue(i) => Ok(duckdb::types::ToSqlOutput::from(*i)), + scalar_value::Kind::UintValue(u) => Ok(duckdb::types::ToSqlOutput::from(*u)), + scalar_value::Kind::DoubleValue(d) => Ok(duckdb::types::ToSqlOutput::from(*d)), + scalar_value::Kind::DecimalValue(d) => { + Ok(duckdb::types::ToSqlOutput::from(d.value.to_owned())) + } + scalar_value::Kind::StrValue(s) => Ok(duckdb::types::ToSqlOutput::from(s.to_owned())), + scalar_value::Kind::DatetimeValue(dt) => u32::try_from(dt.nanos) + .map_err(|err| duckdb::Error::ToSqlConversionFailure(Box::new(err))) + .and_then(|nanos| { + chrono::DateTime::from_timestamp(dt.seconds, nanos).ok_or_else(|| { + duckdb::Error::ToSqlConversionFailure(Box::new( + crate::error::Error::InvalidRequest(format!( + "invalid timestamp {}", + dt + )), + )) + }) + }) + .map(|dt| duckdb::types::ToSqlOutput::from(dt.format("%F %T%.f").to_string())), // refer to https://github.com/duckdb/duckdb-rs/blob/main/crates/duckdb/src/types/chrono.rs#L51 + scalar_value::Kind::DateValue(dt) => { + chrono::NaiveDate::from_ymd_opt(dt.year, dt.month, dt.day) + .ok_or_else(|| { + duckdb::Error::ToSqlConversionFailure(Box::new( + crate::error::Error::InvalidRequest(format!( + "invalid timestamp {}-{}-{}", + dt.year, dt.month, dt.day + )), + )) + }) + .map(|dt| duckdb::types::ToSqlOutput::from(dt.format("%F").to_string())) + // refer to https://github.com/duckdb/duckdb-rs/blob/main/crates/duckdb/src/types/chrono.rs#L17 + } + scalar_value::Kind::TimeValue(time) => chrono::NaiveTime::from_hms_nano_opt( + time.hours, + time.minutes, + time.seconds, + time.nanos, + ) + .ok_or_else(|| { + duckdb::Error::ToSqlConversionFailure(Box::new( + crate::error::Error::InvalidRequest(format!( + "invalid timestamp {}:{}:{}.{}", + time.hours, time.minutes, time.seconds, time.nanos + )), + )) + }) + .map(|time| duckdb::types::ToSqlOutput::from(time.format("%T%.f").to_string())), // refer to https://github.com/duckdb/duckdb-rs/blob/main/crates/duckdb/src/types/chrono.rs#L34 + scalar_value::Kind::IntervalValue(interval) => Ok( + duckdb::types::ToSqlOutput::Borrowed(duckdb::types::ValueRef::Interval { + months: interval.months, + days: interval.days, + nanos: interval.nanos, + }), + ), + } + } +} + +impl TryInto>> for Params { + type Error = crate::error::Error; + + fn try_into(self) -> Result>, Self::Error> { + Ok(duckdb::params_from_iter( + self.params + .into_iter() + .map(|param: ScalarValue| match param.kind { + Some(k) => Ok(k), + None => Err(crate::error::Error::InvalidRequest(format!( + "Invalid param: {:?}", + param + ))), + }) + .collect::, crate::error::Error>>()?, + )) + } +} + +impl duckdb::types::FromSql for scalar_value::Kind { + fn column_result(value: duckdb::types::ValueRef<'_>) -> duckdb::types::FromSqlResult { + match value { + duckdb::types::ValueRef::Null => Ok(scalar_value::Kind::NullValue( + prost_types::NullValue::NullValue as i32, + )), + duckdb::types::ValueRef::Boolean(v) => Ok(scalar_value::Kind::BoolValue(v)), + duckdb::types::ValueRef::TinyInt(v) => Ok(scalar_value::Kind::IntValue(i64::from(v))), + duckdb::types::ValueRef::SmallInt(v) => Ok(scalar_value::Kind::IntValue(i64::from(v))), + duckdb::types::ValueRef::Int(v) => Ok(scalar_value::Kind::IntValue(i64::from(v))), + duckdb::types::ValueRef::BigInt(v) => Ok(scalar_value::Kind::IntValue(v)), + duckdb::types::ValueRef::HugeInt(v) => Err(duckdb::types::FromSqlError::OutOfRange(v)), + duckdb::types::ValueRef::UTinyInt(v) => Ok(scalar_value::Kind::UintValue(u64::from(v))), + duckdb::types::ValueRef::USmallInt(v) => { + Ok(scalar_value::Kind::UintValue(u64::from(v))) + } + duckdb::types::ValueRef::UInt(v) => Ok(scalar_value::Kind::UintValue(u64::from(v))), + duckdb::types::ValueRef::UBigInt(v) => Ok(scalar_value::Kind::UintValue(v)), + duckdb::types::ValueRef::Float(v) => Ok(scalar_value::Kind::DoubleValue(f64::from(v))), + duckdb::types::ValueRef::Double(v) => Ok(scalar_value::Kind::DoubleValue(v)), + duckdb::types::ValueRef::Decimal(v) => Ok(scalar_value::Kind::DecimalValue(Decimal { + value: prost::alloc::string::String::from(v.to_string()), + })), + duckdb::types::ValueRef::Timestamp(..) => chrono::NaiveDateTime::column_result(value) + .and_then(|dt| { + let utc = dt.and_utc(); + let seconds = utc.timestamp(); + let nanos = utc + .timestamp_nanos_opt() + .unwrap_or_else(|| utc.timestamp_micros() * 1000) + % 1000000000; + + i32::try_from(nanos) + .map_err(|_| { + duckdb::types::FromSqlError::OutOfRange(i128::from( + utc.timestamp_micros() * 1000, + )) + }) + .map(|nanos| prost_types::Timestamp { + seconds: seconds, + nanos: nanos, + }) + }) + .map(|timestamp| scalar_value::Kind::DatetimeValue(timestamp)), + duckdb::types::ValueRef::Date32(_) => { + chrono::NaiveDate::column_result(value).map(|dt| { + scalar_value::Kind::DateValue(Date { + year: dt.year(), + month: dt.month(), + day: dt.day(), + }) + }) + } + duckdb::types::ValueRef::Time64(..) => { + chrono::NaiveTime::column_result(value).map(|time| { + scalar_value::Kind::TimeValue(Time { + hours: time.hour(), + minutes: time.minute(), + seconds: time.second(), + nanos: time.nanosecond(), + }) + }) + } + duckdb::types::ValueRef::Interval { + months, + days, + nanos, + } => Ok(scalar_value::Kind::IntervalValue(Interval { + months, + days, + nanos, + })), + duckdb::types::ValueRef::Text(v) => String::from_utf8(v.to_owned()) + .map(|str| scalar_value::Kind::StrValue(str)) + .map_err(|err| { + duckdb::types::FromSqlError::Other(Box::new(crate::error::Error::internal( + err.to_string(), + ))) + }), + other => Err(duckdb::types::FromSqlError::Other(Box::new( + crate::error::Error::unsupported_type(other.data_type()), + ))), + } + } +} diff --git a/src/service.rs b/src/service.rs index b83a8c9..de8d157 100644 --- a/src/service.rs +++ b/src/service.rs @@ -3,71 +3,66 @@ use std::pin::Pin; use tokio_stream::{Stream, StreamExt}; -use gduck::db_service_server::{DbService, DbServiceServer}; - -pub mod gduck { - tonic::include_proto!("gduck"); -} - -impl From for duckdb::AccessMode { - fn from(value: gduck::request::connect::Mode) -> Self { - match value { - gduck::request::connect::Mode::Auto => duckdb::AccessMode::Automatic, - gduck::request::connect::Mode::ReadWrite => duckdb::AccessMode::ReadWrite, - gduck::request::connect::Mode::ReadOnly => duckdb::AccessMode::ReadOnly, - } - } -} +use crate::proto; +use crate::proto::db_service_server as grpc; #[derive(Debug)] pub struct DuckDbService {} #[tonic::async_trait] -impl DbService for DuckDbService { +impl grpc::DbService for DuckDbService { type TransactionStream = - Pin> + Send + 'static>>; + Pin> + Send + 'static>>; async fn transaction( &self, - request: tonic::Request>, + request: tonic::Request>, ) -> Result, tonic::Status> { let mut stream = request.into_inner(); - let connection = stream.try_next().await?.and_then(|request| { + let gduck = stream.try_next().await?.and_then(|request| { request.message.map(|message| { - log::info!("{:?}", message); - - if let gduck::request::Message::Connect(c) = message { - duckdb::Connection::open_with_flags( - &c.file_name, - duckdb::Config::default().access_mode(c.mode().into())?, - ) - .map_err(|err| anyhow::Error::from(err)) + if let proto::request::Message::Connect(c) = message { + crate::gduck::Gduck::connect(c) } else { - Err(anyhow::anyhow!( - "Transaction must begin with Connect message." - )) + Err(crate::error::Error::ProtocolError { + message: String::from("Transaction must begin with Connect message."), + }) } }) }); - match connection { - Some(Ok(conn)) => { + match gduck { + Some(Ok(gduck)) => { let output = async_stream::stream! { while let Some(request) = stream.try_next().await? { - match request.message { - Some(gduck::request::Message::Query(q)) => { - let query = q.query; - log::info!("{}", &query); + if let Some(proto::request::Message::Query(q)) = request.message { + let query_result = match q.kind { + Some(proto::query::Kind::Execute(q)) => gduck.execute(q.query, q.params.unwrap_or_default()), + Some(proto::query::Kind::Value(q)) => gduck.query_value(q.query, q.params.unwrap_or_default()), + Some(proto::query::Kind::Rows(q)) => gduck.query_rows(q.query, q.params.unwrap_or_default()), + Some(proto::query::Kind::Ctas(ctas)) => gduck.create_table_as(ctas.table_name, ctas.query, ctas.params.unwrap_or_default()), + Some(proto::query::Kind::Parquet(pq)) => { + match pq.location { + Some(l) => crate::uri::Uri::try_from(l).and_then(|loc| gduck.query_as_parquet(pq.query, pq.params.unwrap_or_default(), loc)), + None => Err(crate::error::Error::InvalidRequest(String::from("Parquet file location is required."))) + } + } + kind => Err(crate::error::Error::ProtocolError { message: format!("Unknown query: {:?}", kind) }) + }; - let result = conn.query_row(&query, [], |row| { row.get::(0) }) - .map_err(|err| { tonic::Status::new(tonic::Code::Unknown, err.to_string()) })?; - yield Ok(gduck::Response{result: Some(gduck::response::QueryResult{result: format!("{:?}", result)})}); - }, - _ => { - yield Err(tonic::Status::new(tonic::Code::Internal, "Unknown type of request received.")); + match query_result { + Ok(result) => { + yield Ok(proto::Response{ result: Some(proto::response::Result::Success(result))}) + }, + Err(err) => { + yield Err(tonic::Status::new(tonic::Code::Internal, err.to_string())); + } } + } else { + yield Err(tonic::Status::new(tonic::Code::Internal, "Unknown type of request received.")); } + } log::info!("DONE"); yield Err(tonic::Status::ok("Completed successfully.")) @@ -87,7 +82,7 @@ impl DuckDbService { DuckDbService {} } - pub fn new_server() -> DbServiceServer { - DbServiceServer::new(DuckDbService::new()) + pub fn new_server() -> grpc::DbServiceServer { + grpc::DbServiceServer::new(DuckDbService::new()) } } diff --git a/src/uri.rs b/src/uri.rs new file mode 100644 index 0000000..53248ff --- /dev/null +++ b/src/uri.rs @@ -0,0 +1,19 @@ +#[derive(Clone, Debug)] +#[allow(dead_code)] +pub enum Uri { + LocalFileSystem(std::path::PathBuf), + S3 { bucket: String, key: String }, + Gcs { bucket: String, key: String }, + Raw(String), +} + +impl std::fmt::Display for Uri { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Uri::LocalFileSystem(path) => write!(f, "{}", path.to_string_lossy()), + Uri::S3 { bucket, key } => write!(f, "s3://{}/{}", bucket, key), + Uri::Gcs { bucket, key } => write!(f, "gs://{}/{}", bucket, key), + Uri::Raw(raw) => write!(f, "{}", raw), + } + } +} From a4b2c89f22c3cb155a481b458e0a12247fcf6db4 Mon Sep 17 00:00:00 2001 From: saint1991 Date: Thu, 10 Oct 2024 00:47:42 +0900 Subject: [PATCH 2/3] replace poetry with uv --- client/.gitignore | 3 +- client/Dockerfile | 13 +- client/README.md | 2 + client/poetry.lock | 458 ------------------------------------------ client/pyproject.toml | 33 ++- client/request.py | 17 +- client/uv.lock | 332 ++++++++++++++++++++++++++++++ 7 files changed, 372 insertions(+), 486 deletions(-) create mode 100644 client/README.md delete mode 100644 client/poetry.lock create mode 100644 client/uv.lock diff --git a/client/.gitignore b/client/.gitignore index ab621de..7398645 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -1,2 +1,3 @@ gen -__pycache__ \ No newline at end of file +__pycache__ +.venv diff --git a/client/Dockerfile b/client/Dockerfile index 4a84113..bc0d4e6 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -1,22 +1,21 @@ # syntax=docker/dockerfile:1 -FROM python:3.12-slim-bookworm AS grpc +FROM python:3.13-slim-bookworm AS grpc RUN apt-get update -y \ && apt-get install -y curl -ENV POETRY_HOME=/etc/poetry -RUN curl -sSL https://install.python-poetry.org | python3 - -ENV PATH=${POETRY_HOME}/bin:${PATH} +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +ENV PATH=/root/.cargo/bin:${PATH} RUN --mount=type=bind,source=client/pyproject.toml,target=/pyproject.toml \ - --mount=type=bind,source=client/poetry.lock,target=/poetry.lock \ - poetry install --only=main + --mount=type=bind,source=client/uv.lock,target=/uv.lock \ + uv pip install --system -r /pyproject.toml RUN --mount=type=bind,source=client/pyproject.toml,target=/pyproject.toml \ --mount=type=bind,source=proto,target=/proto \ mkdir -p /gen \ - && poetry run python -m grpc_tools.protoc -I/proto --python_out=/gen --grpc_python_out=/gen /proto/*.proto + && python -m grpc_tools.protoc -I/proto --python_out=/gen --grpc_python_out=/gen /proto/*.proto FROM scratch AS grpc-out diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..36eadaa --- /dev/null +++ b/client/README.md @@ -0,0 +1,2 @@ +## Python client + diff --git a/client/poetry.lock b/client/poetry.lock deleted file mode 100644 index ea75281..0000000 --- a/client/poetry.lock +++ /dev/null @@ -1,458 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. - -[[package]] -name = "black" -version = "24.8.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "colorlog" -version = "4.8.0" -description = "Log formatting with colors!" -optional = false -python-versions = "*" -files = [ - {file = "colorlog-4.8.0-py2.py3-none-any.whl", hash = "sha256:3dd15cb27e8119a24c1a7b5c93f9f3b455855e0f73993b1c25921b2f646f1dcd"}, - {file = "colorlog-4.8.0.tar.gz", hash = "sha256:59b53160c60902c405cdec28d38356e09d40686659048893e026ecbd589516b1"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} - -[[package]] -name = "dacite" -version = "1.8.1" -description = "Simple creation of data classes from dictionaries." -optional = false -python-versions = ">=3.6" -files = [ - {file = "dacite-1.8.1-py3-none-any.whl", hash = "sha256:cc31ad6fdea1f49962ea42db9421772afe01ac5442380d9a99fcf3d188c61afe"}, -] - -[package.extras] -dev = ["black", "coveralls", "mypy", "pre-commit", "pylint", "pytest (>=5)", "pytest-benchmark", "pytest-cov"] - -[[package]] -name = "flake8" -version = "7.1.1" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.8.1" -files = [ - {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, - {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.12.0,<2.13.0" -pyflakes = ">=3.2.0,<3.3.0" - -[[package]] -name = "gitdb" -version = "4.0.11" -description = "Git Object Database" -optional = false -python-versions = ">=3.7" -files = [ - {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, - {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, -] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.43" -description = "GitPython is a Python library used to interact with Git repositories" -optional = false -python-versions = ">=3.7" -files = [ - {file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"}, - {file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"}, -] - -[package.dependencies] -gitdb = ">=4.0.1,<5" - -[package.extras] -doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] - -[[package]] -name = "grpcio" -version = "1.66.1" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.8" -files = [ - {file = "grpcio-1.66.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:4877ba180591acdf127afe21ec1c7ff8a5ecf0fe2600f0d3c50e8c4a1cbc6492"}, - {file = "grpcio-1.66.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:3750c5a00bd644c75f4507f77a804d0189d97a107eb1481945a0cf3af3e7a5ac"}, - {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:a013c5fbb12bfb5f927444b477a26f1080755a931d5d362e6a9a720ca7dbae60"}, - {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1b24c23d51a1e8790b25514157d43f0a4dce1ac12b3f0b8e9f66a5e2c4c132f"}, - {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7ffb8ea674d68de4cac6f57d2498fef477cef582f1fa849e9f844863af50083"}, - {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:307b1d538140f19ccbd3aed7a93d8f71103c5d525f3c96f8616111614b14bf2a"}, - {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1c17ebcec157cfb8dd445890a03e20caf6209a5bd4ac5b040ae9dbc59eef091d"}, - {file = "grpcio-1.66.1-cp310-cp310-win32.whl", hash = "sha256:ef82d361ed5849d34cf09105d00b94b6728d289d6b9235513cb2fcc79f7c432c"}, - {file = "grpcio-1.66.1-cp310-cp310-win_amd64.whl", hash = "sha256:292a846b92cdcd40ecca46e694997dd6b9be6c4c01a94a0dfb3fcb75d20da858"}, - {file = "grpcio-1.66.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:c30aeceeaff11cd5ddbc348f37c58bcb96da8d5aa93fed78ab329de5f37a0d7a"}, - {file = "grpcio-1.66.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8a1e224ce6f740dbb6b24c58f885422deebd7eb724aff0671a847f8951857c26"}, - {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a66fe4dc35d2330c185cfbb42959f57ad36f257e0cc4557d11d9f0a3f14311df"}, - {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ba04659e4fce609de2658fe4dbf7d6ed21987a94460f5f92df7579fd5d0e22"}, - {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4573608e23f7e091acfbe3e84ac2045680b69751d8d67685ffa193a4429fedb1"}, - {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7e06aa1f764ec8265b19d8f00140b8c4b6ca179a6dc67aa9413867c47e1fb04e"}, - {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3885f037eb11f1cacc41f207b705f38a44b69478086f40608959bf5ad85826dd"}, - {file = "grpcio-1.66.1-cp311-cp311-win32.whl", hash = "sha256:97ae7edd3f3f91480e48ede5d3e7d431ad6005bfdbd65c1b56913799ec79e791"}, - {file = "grpcio-1.66.1-cp311-cp311-win_amd64.whl", hash = "sha256:cfd349de4158d797db2bd82d2020554a121674e98fbe6b15328456b3bf2495bb"}, - {file = "grpcio-1.66.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:a92c4f58c01c77205df6ff999faa008540475c39b835277fb8883b11cada127a"}, - {file = "grpcio-1.66.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fdb14bad0835914f325349ed34a51940bc2ad965142eb3090081593c6e347be9"}, - {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f03a5884c56256e08fd9e262e11b5cfacf1af96e2ce78dc095d2c41ccae2c80d"}, - {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ca2559692d8e7e245d456877a85ee41525f3ed425aa97eb7a70fc9a79df91a0"}, - {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ca1be089fb4446490dd1135828bd42a7c7f8421e74fa581611f7afdf7ab761"}, - {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d639c939ad7c440c7b2819a28d559179a4508783f7e5b991166f8d7a34b52815"}, - {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b9feb4e5ec8dc2d15709f4d5fc367794d69277f5d680baf1910fc9915c633524"}, - {file = "grpcio-1.66.1-cp312-cp312-win32.whl", hash = "sha256:7101db1bd4cd9b880294dec41a93fcdce465bdbb602cd8dc5bd2d6362b618759"}, - {file = "grpcio-1.66.1-cp312-cp312-win_amd64.whl", hash = "sha256:b0aa03d240b5539648d996cc60438f128c7f46050989e35b25f5c18286c86734"}, - {file = "grpcio-1.66.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:ecfe735e7a59e5a98208447293ff8580e9db1e890e232b8b292dc8bd15afc0d2"}, - {file = "grpcio-1.66.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4825a3aa5648010842e1c9d35a082187746aa0cdbf1b7a2a930595a94fb10fce"}, - {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:f517fd7259fe823ef3bd21e508b653d5492e706e9f0ef82c16ce3347a8a5620c"}, - {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1fe60d0772831d96d263b53d83fb9a3d050a94b0e94b6d004a5ad111faa5b5b"}, - {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31a049daa428f928f21090403e5d18ea02670e3d5d172581670be006100db9ef"}, - {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f914386e52cbdeb5d2a7ce3bf1fdfacbe9d818dd81b6099a05b741aaf3848bb"}, - {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bff2096bdba686019fb32d2dde45b95981f0d1490e054400f70fc9a8af34b49d"}, - {file = "grpcio-1.66.1-cp38-cp38-win32.whl", hash = "sha256:aa8ba945c96e73de29d25331b26f3e416e0c0f621e984a3ebdb2d0d0b596a3b3"}, - {file = "grpcio-1.66.1-cp38-cp38-win_amd64.whl", hash = "sha256:161d5c535c2bdf61b95080e7f0f017a1dfcb812bf54093e71e5562b16225b4ce"}, - {file = "grpcio-1.66.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:d0cd7050397b3609ea51727b1811e663ffda8bda39c6a5bb69525ef12414b503"}, - {file = "grpcio-1.66.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0e6c9b42ded5d02b6b1fea3a25f036a2236eeb75d0579bfd43c0018c88bf0a3e"}, - {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:c9f80f9fad93a8cf71c7f161778ba47fd730d13a343a46258065c4deb4b550c0"}, - {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dd67ed9da78e5121efc5c510f0122a972216808d6de70953a740560c572eb44"}, - {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48b0d92d45ce3be2084b92fb5bae2f64c208fea8ceed7fccf6a7b524d3c4942e"}, - {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d813316d1a752be6f5c4360c49f55b06d4fe212d7df03253dfdae90c8a402bb"}, - {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c9bebc6627873ec27a70fc800f6083a13c70b23a5564788754b9ee52c5aef6c"}, - {file = "grpcio-1.66.1-cp39-cp39-win32.whl", hash = "sha256:30a1c2cf9390c894c90bbc70147f2372130ad189cffef161f0432d0157973f45"}, - {file = "grpcio-1.66.1-cp39-cp39-win_amd64.whl", hash = "sha256:17663598aadbedc3cacd7bbde432f541c8e07d2496564e22b214b22c7523dac8"}, - {file = "grpcio-1.66.1.tar.gz", hash = "sha256:35334f9c9745add3e357e3372756fd32d925bd52c41da97f4dfdafbde0bf0ee2"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.66.1)"] - -[[package]] -name = "grpcio-tools" -version = "1.66.1" -description = "Protobuf code generator for gRPC" -optional = false -python-versions = ">=3.8" -files = [ - {file = "grpcio_tools-1.66.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:e0c71405399ef59782600b1f0bdebc69ba12d7c9527cd268162a86273971d294"}, - {file = "grpcio_tools-1.66.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:df1a174a6f9d3b4c380f005f33352d2e95464f33f021fb08084735a2eb6e23b1"}, - {file = "grpcio_tools-1.66.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:7d789bfe53fce9e87aa80c3694a366258ce4c41b706258e9228ed4994832b780"}, - {file = "grpcio_tools-1.66.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95c44a265ff01fd05166edae9350bc2e7d1d9a95e8f53b8cd04d2ae0a588c583"}, - {file = "grpcio_tools-1.66.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b962a8767c3c0f9afe92e0dd6bb0b2305d35195a1053f84d4d31f585b87557ed"}, - {file = "grpcio_tools-1.66.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d8616773126ec3cdf747b06a12e957b43ac15c34e4728def91fa67249a7c689a"}, - {file = "grpcio_tools-1.66.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0067e79b6001560ac6acc78cca11fd3504fa27f8af46e3cdbac2f4998505e597"}, - {file = "grpcio_tools-1.66.1-cp310-cp310-win32.whl", hash = "sha256:fa4f95a79a34afc3b5464895d091cd1911227fc3ab0441b9a37cd1817cf7db86"}, - {file = "grpcio_tools-1.66.1-cp310-cp310-win_amd64.whl", hash = "sha256:3acce426f5e643de63019311171f4d31131da8149de518716a95c29a2c12dd38"}, - {file = "grpcio_tools-1.66.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:9a07e24feb7472419cf70ebbb38dd4299aea696f91f191b62a99b3ee9ff03f89"}, - {file = "grpcio_tools-1.66.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:097a069e7c640043921ecaf3e88d7af78ccd40c25dbddc91db2a4a2adbd0393d"}, - {file = "grpcio_tools-1.66.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:016fa273dc696c9d8045091ac50e000bce766183a6b150801f51c2946e33dbe3"}, - {file = "grpcio_tools-1.66.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ec9f4f964f8e8ed5e9cc13deb678c83d5597074c256805373220627833bc5ad"}, - {file = "grpcio_tools-1.66.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3198815814cdd12bdb69b7580d7770a4ad4c8b2093e0bd6b987bc817618e3eec"}, - {file = "grpcio_tools-1.66.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:796620fc41d3fbb566d9614ef22bc55df67fac1f1e19c1e0fb6ec48bc9b6a44b"}, - {file = "grpcio_tools-1.66.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:222d8dc218560698e1abf652fb47e4015994ec7a265ef46e012fd9c9e77a4d6b"}, - {file = "grpcio_tools-1.66.1-cp311-cp311-win32.whl", hash = "sha256:56e17a11f34df252b4c6fb8aa8cd7b44d162dba9f3333be87ddf7c8bf496622a"}, - {file = "grpcio_tools-1.66.1-cp311-cp311-win_amd64.whl", hash = "sha256:edd52d667f2aa3c73233be0a821596937f24536647c12d96bfc54aa4cb04747d"}, - {file = "grpcio_tools-1.66.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:869b6960d5daffda0dac1a474b44144f0dace0d4336394e499c4f400c5e2f8d9"}, - {file = "grpcio_tools-1.66.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:68d9390bf9ba863ac147fc722d6548caa587235e887cac1bc2438212e89d1de7"}, - {file = "grpcio_tools-1.66.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:b8660401beca7e3af28722439e07b0bcdca80b4a68f5a5a1138ae7b7780a6abf"}, - {file = "grpcio_tools-1.66.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb67b9aa9cd69468bceb933e8e0f89fd13695746c018c4d2e6b3b84e73f3ad97"}, - {file = "grpcio_tools-1.66.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5daceb9716e31edc0e1ba0f93303785211438c43502edddad7a919fc4cb3d664"}, - {file = "grpcio_tools-1.66.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0a86398a4cd0665bc7f09fa90b89bac592c959d2c895bf3cf5d47a98c0f2d24c"}, - {file = "grpcio_tools-1.66.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1b4acb53338072ab3023e418a5c7059cb15686abd1607516fa1453406dd5f69d"}, - {file = "grpcio_tools-1.66.1-cp312-cp312-win32.whl", hash = "sha256:88e04b7546101bc79c868c941777efd5088063a9e4f03b4d7263dde796fbabf7"}, - {file = "grpcio_tools-1.66.1-cp312-cp312-win_amd64.whl", hash = "sha256:5b4fc56abeafae74140f5da29af1093e88ce64811d77f1a81c3146e9e996fb6a"}, - {file = "grpcio_tools-1.66.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:d4dd2ff982c1aa328ef47ce34f07af82f1f13599912fb1618ebc5fe1e14dddb8"}, - {file = "grpcio_tools-1.66.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:066648543f786cb74b1fef5652359952455dbba37e832642026fd9fd8a219b5f"}, - {file = "grpcio_tools-1.66.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:d19d47744c30e6bafa76b3113740e71f382d75ebb2918c1efd62ebe6ba7e20f9"}, - {file = "grpcio_tools-1.66.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:739c53571130b359b738ac7d6d0a1f772e15779b66df7e6764bee4071cd38689"}, - {file = "grpcio_tools-1.66.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2226ff8d3ecba83b7622946df19d6e8e15cb52f761b8d9e2f807b228db5f1b1e"}, - {file = "grpcio_tools-1.66.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f4b1498cb8b422fbae32a491c9154e8d47650caf5852fbe6b3b34253e824343"}, - {file = "grpcio_tools-1.66.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:93d2d9e14e81affdc63d67c42eb16a8da1b6fecc16442a703ca60eb0e7591691"}, - {file = "grpcio_tools-1.66.1-cp38-cp38-win32.whl", hash = "sha256:d761dfd97a10e4aae73628b5120c64e56f0cded88651d0003d2d80e678c3e7c9"}, - {file = "grpcio_tools-1.66.1-cp38-cp38-win_amd64.whl", hash = "sha256:e1c2ac0955f5fb87b8444316e475242d194c3f3cd0b7b6e54b889a7b6f05156f"}, - {file = "grpcio_tools-1.66.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:5f1f04578b72c281e39274348a61d240c48d5321ba8d7a8838e194099ecbc322"}, - {file = "grpcio_tools-1.66.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:da9b0c08dbbf07535ee1b75a22d0acc5675a808a3a3df9f9b21e0e73ddfbb3a9"}, - {file = "grpcio_tools-1.66.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:e302b4e1fa856d74ff65c65888b3a37153287ce6ad5bad80b2fdf95130accec2"}, - {file = "grpcio_tools-1.66.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7fc3f62494f238774755ff90f0e66a93ac7972ea1eb7180c45acf4fd53b25cca"}, - {file = "grpcio_tools-1.66.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23cad65ff22459aa387f543d293f54834c9aac8f76fb7416a7046556df75b567"}, - {file = "grpcio_tools-1.66.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3d17a27c567a5e4d18f487368215cb51b43e2499059fd6113b92f7ae1fee48be"}, - {file = "grpcio_tools-1.66.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4df167e67b083f96bc277032a526f6186e98662aaa49baea1dfb8ecfe26ce117"}, - {file = "grpcio_tools-1.66.1-cp39-cp39-win32.whl", hash = "sha256:f94d5193b2f2a9595795b83e7978b2bee1c0399da66f2f24d179c388f81fb99c"}, - {file = "grpcio_tools-1.66.1-cp39-cp39-win_amd64.whl", hash = "sha256:66f527a1e3f063065e29cf6f3e55892434d13a5a51e3b22402e09da9521e98a3"}, - {file = "grpcio_tools-1.66.1.tar.gz", hash = "sha256:5055ffe840ea8f505c30378be02afb4dbecb33480e554debe10b63d6b2f641c3"}, -] - -[package.dependencies] -grpcio = ">=1.66.1" -protobuf = ">=5.26.1,<6.0dev" -setuptools = "*" - -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "packaging" -version = "24.1" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "platformdirs" -version = "4.3.3" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.3.3-py3-none-any.whl", hash = "sha256:50a5450e2e84f44539718293cbb1da0a0885c9d14adf21b77bae4e66fc99d9b5"}, - {file = "platformdirs-4.3.3.tar.gz", hash = "sha256:d4e0b7d8ec176b341fb03cb11ca12d0276faa8c485f9cd218f613840463fc2c0"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] - -[[package]] -name = "protobuf" -version = "5.28.1" -description = "" -optional = false -python-versions = ">=3.8" -files = [ - {file = "protobuf-5.28.1-cp310-abi3-win32.whl", hash = "sha256:fc063acaf7a3d9ca13146fefb5b42ac94ab943ec6e978f543cd5637da2d57957"}, - {file = "protobuf-5.28.1-cp310-abi3-win_amd64.whl", hash = "sha256:4c7f5cb38c640919791c9f74ea80c5b82314c69a8409ea36f2599617d03989af"}, - {file = "protobuf-5.28.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4304e4fceb823d91699e924a1fdf95cde0e066f3b1c28edb665bda762ecde10f"}, - {file = "protobuf-5.28.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:0dfd86d2b5edf03d91ec2a7c15b4e950258150f14f9af5f51c17fa224ee1931f"}, - {file = "protobuf-5.28.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:51f09caab818707ab91cf09cc5c156026599cf05a4520779ccbf53c1b352fb25"}, - {file = "protobuf-5.28.1-cp38-cp38-win32.whl", hash = "sha256:1b04bde117a10ff9d906841a89ec326686c48ececeb65690f15b8cabe7149495"}, - {file = "protobuf-5.28.1-cp38-cp38-win_amd64.whl", hash = "sha256:cabfe43044ee319ad6832b2fda332646f9ef1636b0130186a3ae0a52fc264bb4"}, - {file = "protobuf-5.28.1-cp39-cp39-win32.whl", hash = "sha256:4b4b9a0562a35773ff47a3df823177ab71a1f5eb1ff56d8f842b7432ecfd7fd2"}, - {file = "protobuf-5.28.1-cp39-cp39-win_amd64.whl", hash = "sha256:f24e5d70e6af8ee9672ff605d5503491635f63d5db2fffb6472be78ba62efd8f"}, - {file = "protobuf-5.28.1-py3-none-any.whl", hash = "sha256:c529535e5c0effcf417682563719e5d8ac8d2b93de07a56108b4c2d436d7a29a"}, - {file = "protobuf-5.28.1.tar.gz", hash = "sha256:42597e938f83bb7f3e4b35f03aa45208d49ae8d5bcb4bc10b9fc825e0ab5e423"}, -] - -[[package]] -name = "pycodestyle" -version = "2.12.1" -description = "Python style guide checker" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, - {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, -] - -[[package]] -name = "pyflakes" -version = "3.2.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, - {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, -] - -[[package]] -name = "pysen" -version = "0.11.0" -description = "Python linting made easy. Also a casual yet honorific way to address individuals who have entered an organization prior to you." -optional = false -python-versions = "*" -files = [ - {file = "pysen-0.11.0-py3-none-any.whl", hash = "sha256:d4e81e12f9f1b4cf25df0399d10b0201a9a4bae18846fe0e279179b657daf015"}, - {file = "pysen-0.11.0.tar.gz", hash = "sha256:b9ea6c67289360f7bd9c95f8c79f603a775decb9342d2635dc40fcda92dfa53a"}, -] - -[package.dependencies] -colorlog = ">=4.0.0,<5.0.0" -dacite = ">=1.1.0,<2.0.0" -GitPython = ">=3.0.0,<4.0.0" -tomlkit = ">=0.5.11,<1.0.0" -unidiff = ">=0.6.0,<1.0.0" - -[package.extras] -lint = ["black (>=19.10b0,<=22.10)", "flake8 (>=3.7,<5)", "flake8-bugbear", "isort (>=4.3,<5.2.0)", "mypy (>=0.770,<0.800)"] - -[[package]] -name = "setuptools" -version = "75.0.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-75.0.0-py3-none-any.whl", hash = "sha256:791ae94f04f78c880b5e614e560dd32d4b4af5d151bd9e7483e3377846caf90a"}, - {file = "setuptools-75.0.0.tar.gz", hash = "sha256:25af69c809d9334cd8e653d385277abeb5a102dca255954005a7092d282575ea"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] - -[[package]] -name = "smmap" -version = "5.0.1" -description = "A pure Python implementation of a sliding window memory map manager" -optional = false -python-versions = ">=3.7" -files = [ - {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, - {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, -] - -[[package]] -name = "tomlkit" -version = "0.13.2" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, - {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, -] - -[[package]] -name = "unidiff" -version = "0.7.5" -description = "Unified diff parsing/metadata extraction library." -optional = false -python-versions = "*" -files = [ - {file = "unidiff-0.7.5-py2.py3-none-any.whl", hash = "sha256:c93bf2265cc1ba2a520e415ab05da587370bc2a3ae9e0414329f54f0c2fc09e8"}, - {file = "unidiff-0.7.5.tar.gz", hash = "sha256:2e5f0162052248946b9f0970a40e9e124236bf86c82b70821143a6fc1dea2574"}, -] - -[metadata] -lock-version = "2.0" -python-versions = "^3.12" -content-hash = "8394e3d57f929dff3cd97c42cf80d456ad0a283300078e5fa1f1c6d6cd390101" diff --git a/client/pyproject.toml b/client/pyproject.toml index 784a899..d71d920 100644 --- a/client/pyproject.toml +++ b/client/pyproject.toml @@ -1,20 +1,12 @@ -[tool.poetry] +[project] name = "client" version = "0.1.0" -description = "" -authors = ["saint1991 "] -readme = "README.md" - -[tool.poetry.dependencies] -python = "^3.12" -grpcio = "^1.66.1" -grpcio-tools = "^1.66.1" - -[tool.poetry.group.dev.dependencies] -pysen = "0.11" -black = "^24.8.0" -flake8 = "^7.1.1" -isort = "^5.13.2" +requires-python = ">=3.13" +dependencies = [ + "grpcio-tools>=1.66.2", + "grpcio>=1.66.2", + "python-dateutil>=2.9.0.post0", +] [tool.pysen] version = "0.11" @@ -27,6 +19,11 @@ enable_mypy = false line_length = 140 py_version = "py312" -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[tool.uv] +dev-dependencies = [ + "black>=24.10.0", + "flake8>=7.1.1", + "isort>=5.13.2", + "pysen>=0.11.0", +] + diff --git a/client/request.py b/client/request.py index 2d8440f..fc09c19 100644 --- a/client/request.py +++ b/client/request.py @@ -1,12 +1,14 @@ +import math from datetime import date, datetime, time, timedelta from decimal import Decimal from typing import Literal, TypeAlias -from database_pb2 import Connect, DataType +from database_pb2 import Connect, DataType, Date from database_pb2 import Decimal as ProtoDecimal -from database_pb2 import Params, Row, Rows, ScalarValue, Schema +from database_pb2 import Interval, Params, Row, Rows, ScalarValue, Schema, Time from error_pb2 import Error, ErrorCode from google.protobuf.struct_pb2 import NULL_VALUE +from google.protobuf.timestamp_pb2 import Timestamp from location_pb2 import Location from query_pb2 import Query @@ -42,3 +44,14 @@ def _value(v: Value) -> ScalarValue: return ScalarValue(decimal_value=ProtoDecimal(value=str(v))) elif type(v) is str: return ScalarValue(str_value=v) + elif type(v) is datetime: + fraction, seconds = math.modf(v.timestamp()) + return ScalarValue(datetime_value=Timestamp(seconds=int(seconds), nanos=int(fraction * 1000000000))) + elif type(v) is date: + return ScalarValue(date_value=Date(year=v.year, month=v.month, day=v.day)) + elif type(v) is time: + return ScalarValue(time_value=Time(hours=v.hour, minutes=v.minute, seconds=v.second, nanos=v.microsecond * 1000)) + elif type(v) is timedelta: + pass + else: + raise ValueError(f"Invalid type of value: {v} ({type(v)})") diff --git a/client/uv.lock b/client/uv.lock new file mode 100644 index 0000000..e659bd9 --- /dev/null +++ b/client/uv.lock @@ -0,0 +1,332 @@ +version = 1 +requires-python = ">=3.13" + +[[package]] +name = "black" +version = "24.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/0d/cc2fb42b8c50d80143221515dd7e4766995bd07c56c9a3ed30baf080b6dc/black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", size = 645813 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/a0/a993f58d4ecfba035e61fca4e9f64a2ecae838fc9f33ab798c62173ed75c/black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", size = 1643986 }, + { url = "https://files.pythonhosted.org/packages/37/d5/602d0ef5dfcace3fb4f79c436762f130abd9ee8d950fa2abdbf8bbc555e0/black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", size = 1448085 }, + { url = "https://files.pythonhosted.org/packages/47/6d/a3a239e938960df1a662b93d6230d4f3e9b4a22982d060fc38c42f45a56b/black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", size = 1760928 }, + { url = "https://files.pythonhosted.org/packages/dd/cf/af018e13b0eddfb434df4d9cd1b2b7892bab119f7a20123e93f6910982e8/black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", size = 1436875 }, + { url = "https://files.pythonhosted.org/packages/8d/a7/4b27c50537ebca8bec139b872861f9d2bf501c5ec51fcf897cb924d9e264/black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", size = 206898 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "client" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "grpcio" }, + { name = "grpcio-tools" }, + { name = "python-dateutil" }, +] + +[package.dev-dependencies] +dev = [ + { name = "black" }, + { name = "flake8" }, + { name = "isort" }, + { name = "pysen" }, +] + +[package.metadata] +requires-dist = [ + { name = "grpcio", specifier = ">=1.66.2" }, + { name = "grpcio-tools", specifier = ">=1.66.2" }, + { name = "python-dateutil", specifier = ">=2.9.0.post0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "black", specifier = ">=24.10.0" }, + { name = "flake8", specifier = ">=7.1.1" }, + { name = "isort", specifier = ">=5.13.2" }, + { name = "pysen", specifier = ">=0.11.0" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "colorlog" +version = "4.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/32/cdfba08674d72fe7895a8ec7be8f171e8502274999cae9497e4545404873/colorlog-4.8.0.tar.gz", hash = "sha256:59b53160c60902c405cdec28d38356e09d40686659048893e026ecbd589516b1", size = 28770 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/62/61449c6bb74c2a3953c415b2cdb488e4f0518ac67b35e2b03a6d543035ca/colorlog-4.8.0-py2.py3-none-any.whl", hash = "sha256:3dd15cb27e8119a24c1a7b5c93f9f3b455855e0f73993b1c25921b2f646f1dcd", size = 10023 }, +] + +[[package]] +name = "dacite" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/0f/cf0943f4f55f0fbc7c6bd60caf1343061dff818b02af5a0d444e473bb78d/dacite-1.8.1-py3-none-any.whl", hash = "sha256:cc31ad6fdea1f49962ea42db9421772afe01ac5442380d9a99fcf3d188c61afe", size = 14309 }, +] + +[[package]] +name = "flake8" +version = "7.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/72/e8d66150c4fcace3c0a450466aa3480506ba2cae7b61e100a2613afc3907/flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38", size = 48054 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/42/65004373ac4617464f35ed15931b30d764f53cdd30cc78d5aea349c8c050/flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213", size = 57731 }, +] + +[[package]] +name = "gitdb" +version = "4.0.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/0d/bbb5b5ee188dec84647a4664f3e11b06ade2bde568dbd489d9d64adef8ed/gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b", size = 394469 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/5b/8f0c4a5bb9fd491c277c21eff7ccae71b47d43c4446c9d0c6cff2fe8c2c4/gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4", size = 62721 }, +] + +[[package]] +name = "gitpython" +version = "3.1.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/a1/106fd9fa2dd989b6fb36e5893961f82992cf676381707253e0bf93eb1662/GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c", size = 214149 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/bd/cc3a402a6439c15c3d4294333e13042b915bbeab54edc457c723931fed3f/GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff", size = 207337 }, +] + +[[package]] +name = "grpcio" +version = "1.66.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/d1/49a96df4eb1d805cf546247df40636515416d2d5c66665e5129c8b4162a8/grpcio-1.66.2.tar.gz", hash = "sha256:563588c587b75c34b928bc428548e5b00ea38c46972181a4d8b75ba7e3f24231", size = 12489713 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/31/8708a8dfb3f1ac89926c27c5dd17412764157a2959dbc5a606eaf8ac71f6/grpcio-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:3b00efc473b20d8bf83e0e1ae661b98951ca56111feb9b9611df8efc4fe5d55d", size = 5004245 }, + { url = "https://files.pythonhosted.org/packages/8b/37/0b57c3769efb3cc9ec97fcaa9f7243046660e7ed58c0faebc4ef315df92c/grpcio-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1caa38fb22a8578ab8393da99d4b8641e3a80abc8fd52646f1ecc92bcb8dee34", size = 10756749 }, + { url = "https://files.pythonhosted.org/packages/bf/5a/425e995724a19a1b110340ed653bc7c5de8019d9fc84b3798a0f79c3eb31/grpcio-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c408f5ef75cfffa113cacd8b0c0e3611cbfd47701ca3cdc090594109b9fcbaed", size = 5499666 }, + { url = "https://files.pythonhosted.org/packages/2e/e4/86a5c5ec40a6b683671a1d044ebca433812d99da8fcfc2889e9c43cecbd4/grpcio-1.66.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c806852deaedee9ce8280fe98955c9103f62912a5b2d5ee7e3eaa284a6d8d8e7", size = 6109578 }, + { url = "https://files.pythonhosted.org/packages/2f/86/a86742f3deaa22385c3bff984c5947fc62d47d3fab26c508730037d027e5/grpcio-1.66.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f145cc21836c332c67baa6fc81099d1d27e266401565bf481948010d6ea32d46", size = 5763274 }, + { url = "https://files.pythonhosted.org/packages/c3/61/b9a2a4345dea0a354c4ed8ac7aacbdd0ff986acbc8f92680213cf3d2faa3/grpcio-1.66.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:73e3b425c1e155730273f73e419de3074aa5c5e936771ee0e4af0814631fb30a", size = 6450416 }, + { url = "https://files.pythonhosted.org/packages/50/b9/ad303ce75d8cd71d855a661519aa160ce42f27498f589f1ae6d9f8c5e8ac/grpcio-1.66.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c509a4f78114cbc5f0740eb3d7a74985fd2eff022971bc9bc31f8bc93e66a3b", size = 6040045 }, + { url = "https://files.pythonhosted.org/packages/ac/b3/8db1873e3240ef1672ba87b89e949ece367089e29e4d221377bfdd288bd3/grpcio-1.66.2-cp313-cp313-win32.whl", hash = "sha256:20657d6b8cfed7db5e11b62ff7dfe2e12064ea78e93f1434d61888834bc86d75", size = 3537126 }, + { url = "https://files.pythonhosted.org/packages/a2/df/133216989fe7e17caeafd7ff5b17cc82c4e722025d0b8d5d2290c11fe2e6/grpcio-1.66.2-cp313-cp313-win_amd64.whl", hash = "sha256:fb70487c95786e345af5e854ffec8cb8cc781bcc5df7930c4fbb7feaa72e1cdf", size = 4278018 }, +] + +[[package]] +name = "grpcio-tools" +version = "1.66.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/31/bc5b5cc2552a58690eb90a6dcfa64d4ff4bd5dcce03711e4975ea683a3e3/grpcio_tools-1.66.2.tar.gz", hash = "sha256:4a36e07913d26ba5ccfd2685ba63ca97f26b08c249d2cc9e74dda37efa49d7e4", size = 5158759 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/c6/9048242cf5c32899c3675bdea6d78a01e0a5411d1d2c012f90d1fe407718/grpcio_tools-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:7e8c9aa91a9e51199048202e3c54491e0a89fb3ac47dde36ff2964fbcee143a3", size = 2307453 }, + { url = "https://files.pythonhosted.org/packages/72/f5/337d4f6009e39f5106963dd289836f5e722552b3ebeb808d9270aa777678/grpcio_tools-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0eaedd3c77824c3762b728c485f91097a58116fa135f3bbc24703621476cd866", size = 5517964 }, + { url = "https://files.pythonhosted.org/packages/d9/49/de2583c81fded5ce3bbfb62b00fe98e346d6197f5102da54591f3e6078d0/grpcio_tools-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:a14007902fb6565c21815da4177105ec905ef37f0550190c4d1bbeb2928c6560", size = 2281082 }, + { url = "https://files.pythonhosted.org/packages/f7/50/305eef404c515ee8dab72b3ac09fa9ff43ad309e2990c46b6b22356d7112/grpcio_tools-1.66.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df8f098bb92d192230f3b23df514b139f3549e2a4390d1f0f0d8ff89de458c54", size = 2616925 }, + { url = "https://files.pythonhosted.org/packages/1d/0f/273d7ac9c7d99b56abb5841d8aff7ffd148fe01b48c2913c8da3de9438e7/grpcio_tools-1.66.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c68642829368f4f83929e0df571dbbc99f1f1553555d8f98d0582da9f6743d9e", size = 2414642 }, + { url = "https://files.pythonhosted.org/packages/9b/a6/4cb894af0b8244e6afb537f3bc624487a2ee285632ad56249a95782cba94/grpcio_tools-1.66.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:5fd20110d2c7706dfdd95457807acb8c050253be2e272b9f5fb977e87ea44d86", size = 3224331 }, + { url = "https://files.pythonhosted.org/packages/f6/75/332ca1f49ef7c48a31fb88a9e9738ffcbc61718fa2c2c5137d6e187a6af4/grpcio_tools-1.66.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:4b16244be4cff92408eb82901b883a70f3dd902fb7c7f66e2a368271be84cde4", size = 2869639 }, + { url = "https://files.pythonhosted.org/packages/19/d0/9080d3fa949659d148425547db7ad84d675554f6ed1bb113bcbd3572d480/grpcio_tools-1.66.2-cp313-cp313-win32.whl", hash = "sha256:d872ba3bbe9e15b43eeb9310dad5edbf490bb3ab0072a46b3a12fed0234eec23", size = 939996 }, + { url = "https://files.pythonhosted.org/packages/83/84/50b7bd9114e22c7b10003e4862b938d3ae253fdb5027088914443890b277/grpcio_tools-1.66.2-cp313-cp313-win_amd64.whl", hash = "sha256:a2810921218471aab5c8cd20204d3b1886aa8e13b495e882158bb398982cf18e", size = 1089819 }, +] + +[[package]] +name = "isort" +version = "5.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "packaging" +version = "24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, +] + +[[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 = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "protobuf" +version = "5.28.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/a4/4579a61de526e19005ceeb93e478b61d77aa38c8a85ad958ff16a9906549/protobuf-5.28.2.tar.gz", hash = "sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0", size = 422494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/30/231764750e0987755b7b8d66771f161e5f002e165d27b72154c776dbabf7/protobuf-5.28.2-cp310-abi3-win32.whl", hash = "sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d", size = 419662 }, + { url = "https://files.pythonhosted.org/packages/7d/46/3fdf7462160135aee6a530f1ec66665b5b4132fa2e1002ab971bc6ec2589/protobuf-5.28.2-cp310-abi3-win_amd64.whl", hash = "sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132", size = 431479 }, + { url = "https://files.pythonhosted.org/packages/37/45/d2a760580f8f2ed2825ba44cb370e0a4011ddef85e728f46ea3dd565a8a5/protobuf-5.28.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7", size = 414736 }, + { url = "https://files.pythonhosted.org/packages/e6/23/ed718dc18e6a561445ece1e7a17d2dda0c634ad9cf663102b47f10005d8f/protobuf-5.28.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f", size = 316518 }, + { url = "https://files.pythonhosted.org/packages/23/08/a1ce0415a115c2b703bfa798f06f0e43ca91dbe29d6180bf86a9287b15e2/protobuf-5.28.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f", size = 316605 }, + { url = "https://files.pythonhosted.org/packages/9b/55/f24e3b801d2e108c48aa2b1b59bb791b5cffba89465cbbf66fc98de89270/protobuf-5.28.2-py3-none-any.whl", hash = "sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece", size = 169566 }, +] + +[[package]] +name = "pycodestyle" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/aa/210b2c9aedd8c1cbeea31a50e42050ad56187754b34eb214c46709445801/pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521", size = 39232 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/d8/a211b3f85e99a0daa2ddec96c949cac6824bd305b040571b82a03dd62636/pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", size = 31284 }, +] + +[[package]] +name = "pyflakes" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725 }, +] + +[[package]] +name = "pysen" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorlog" }, + { name = "dacite" }, + { name = "gitpython" }, + { name = "tomlkit" }, + { name = "unidiff" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/54/5535af8606eb541771a017a06941b3bba252ad514bd2727d91e3b7616936/pysen-0.11.0.tar.gz", hash = "sha256:b9ea6c67289360f7bd9c95f8c79f603a775decb9342d2635dc40fcda92dfa53a", size = 48083 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/a0/206f4020d6cd6c219e040a35b83ae01fa6e76dfe6ce8044a1876ed285f72/pysen-0.11.0-py3-none-any.whl", hash = "sha256:d4e81e12f9f1b4cf25df0399d10b0201a9a4bae18846fe0e279179b657daf015", size = 58110 }, +] + +[[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 = "setuptools" +version = "75.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/b8/f21073fde99492b33ca357876430822e4800cdf522011f18041351dfa74b/setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538", size = 1348057 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/ae/f19306b5a221f6a436d8f2238d5b80925004093fa3edea59835b514d9057/setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2", size = 1248506 }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +] + +[[package]] +name = "smmap" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/04/b5bf6d21dc4041000ccba7eb17dd3055feb237e7ffc2c20d3fae3af62baa/smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62", size = 22291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a5/10f97f73544edcdef54409f1d839f6049a0d79df68adbc1ceb24d1aaca42/smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da", size = 24282 }, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, +] + +[[package]] +name = "unidiff" +version = "0.7.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/48/81be0ac96e423a877754153699731ef439fd7b80b4c8b5425c94ed079ebd/unidiff-0.7.5.tar.gz", hash = "sha256:2e5f0162052248946b9f0970a40e9e124236bf86c82b70821143a6fc1dea2574", size = 20931 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/54/57c411a6e8f7bd7848c8b66e4dcaffa586bf4c02e63f2280db0327a4e6eb/unidiff-0.7.5-py2.py3-none-any.whl", hash = "sha256:c93bf2265cc1ba2a520e415ab05da587370bc2a3ae9e0414329f54f0c2fc09e8", size = 14386 }, +] From ccb1013a95a241d4a1832237a55c3ec0d0e9db0e Mon Sep 17 00:00:00 2001 From: saint1991 Date: Fri, 11 Oct 2024 01:15:22 +0900 Subject: [PATCH 3/3] add request adapter to python client --- .vscode/launch.json | 2 +- client/gduck/__init__.py | 0 client/{ => gduck}/client.py | 54 ++++++++------ client/gduck/request.py | 104 +++++++++++++++++++++++++++ client/gduck/response.py | 134 +++++++++++++++++++++++++++++++++++ client/gduck/types.py | 53 ++++++++++++++ client/main.py | 9 +-- client/request.py | 57 --------------- 8 files changed, 331 insertions(+), 82 deletions(-) create mode 100644 client/gduck/__init__.py rename client/{ => gduck}/client.py (62%) create mode 100644 client/gduck/request.py create mode 100644 client/gduck/response.py create mode 100644 client/gduck/types.py delete mode 100644 client/request.py diff --git a/.vscode/launch.json b/.vscode/launch.json index e615ed5..3db4437 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "name":"Debug Python client", "type":"debugpy", "request":"launch", - "program":"${workspaceFolder}/client/client.py", + "program":"${workspaceFolder}/client/main.py", "console":"integratedTerminal", "env": { "PYTHONPATH": "${workspaceFolder}/client/gen" diff --git a/client/gduck/__init__.py b/client/gduck/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/client/client.py b/client/gduck/client.py similarity index 62% rename from client/client.py rename to client/gduck/client.py index f83479b..31234ea 100644 --- a/client/client.py +++ b/client/gduck/client.py @@ -2,14 +2,21 @@ import threading from dataclasses import dataclass +from pathlib import Path from queue import SimpleQueue from types import TracebackType -from typing import Literal, Self +from typing import Self import grpc +from error_pb2 import Error from grpc._channel import _MultiThreadedRendezvous +from query_pb2 import Query +from service_pb2 import Request, Response from service_pb2_grpc import DbServiceStub +from .request import ConnectionMode, Value, connect, ctas, execute, local_file, parquet, request, rows, value +from .response import parse_location, parse_rows, parse_value + @dataclass(frozen=True) class Addr: @@ -46,7 +53,10 @@ def __init__(self, responses: _MultiThreadedRendezvous, out: SimpleQueue, group: def run(self) -> None: try: for response in self._responses: - self._out.put(response.result) + if response.HasField("success"): + self._out.put(response.success) + elif response.HasField("error"): + self._out.put(response.error) except _MultiThreadedRendezvous as e: if e.code() != grpc.StatusCode.CANCELLED: raise e @@ -71,10 +81,29 @@ def _request_generator(self): while (request := self._requests.get()) != self._END_STREAM: yield request - def query(self, query: str) -> None: - self._requests.put(self._query_request(query)) + def _query(self, query: Query) -> Response.QueryResult | Error: + self._requests.put(request(query)) return self._results.get() + def execute(self, query: str, *params: tuple[Value]) -> None: + self._query(execute(query, *params)) + + def query_value(self, query: str, *params: tuple[Value]) -> Value: + result = self._query(value(query, *params)) + return parse_value(result.value) + + def query_rows(self, query: str, *params: tuple[Value]) -> Value: + result = self._query(rows(query, *params)) + _, r = parse_rows(result.rows) + return r + + def ctas(self, table_name: str, query: str, *params: tuple[Value]) -> None: + self._query(ctas(table_name, query, *params)) + + def local_parquet(self, file: Path, query: str, *params: tuple[Value]) -> Path: + result = self._query(parquet(local_file(file), query, *params)) + return parse_location(result.parquet_file) + def __enter__(self) -> Self: self._channel = grpc.insecure_channel(target=str(self._addr)) @@ -90,20 +119,5 @@ def __exit__(self, exc_type: type, exc_value: Exception, traceback: TracebackTyp self._channel.close() return False - @property - def mode(self) -> Request.Connect.Mode: - if self._mode == "auto": - return Request.Connect.Mode.MODE_AUTO - elif self._mode == "read_write": - return Request.Connect.Mode.MODE_READ_WRITE - elif self._mode == "read_only": - return Request.Connect.Mode.MODE_READ_ONLY - else: - raise ValueError(f"Unknown mode: {self._mode}") - def _connect_request(self) -> Request: - return Request(connect=Request.Connect(file_name=self._database_file, mode=self.mode)) - - @staticmethod - def _query_request(query: str) -> Request: - return Request(query=Request.Query(query=query)) + return request(kind=connect(file_name=self._database_file, mode=self._mode)) diff --git a/client/gduck/request.py b/client/gduck/request.py new file mode 100644 index 0000000..0671174 --- /dev/null +++ b/client/gduck/request.py @@ -0,0 +1,104 @@ +import math +from datetime import date, datetime, time +from decimal import Decimal +from pathlib import Path +from typing import Literal + +from database_pb2 import Connect, Date +from database_pb2 import Decimal as ProtoDecimal +from database_pb2 import Interval, Params, ScalarValue, Time +from dateutil.relativedelta import relativedelta +from google.protobuf.struct_pb2 import NULL_VALUE +from google.protobuf.timestamp_pb2 import Timestamp +from location_pb2 import Location +from query_pb2 import Query +from service_pb2 import Request + +from .types import Value + +__all__ = ["ConnectionMode", "connect", "local_file", "execute", "value", "rows", "ctas", "parquet", "request"] + +ConnectionMode = Literal["auto", "read_write", "read_only"] + + +def _mode(m: ConnectionMode = Connect.Mode.MODE_AUTO) -> Connect.Mode: + if m == "read_write": + return Connect.Mode.MODE_READ_WRITE + elif m == "read_only": + return Connect.Mode.MODE_READ_ONLY + else: + return Connect.Mode.MODE_AUTO + + +def connect(file_name: str, mode: ConnectionMode) -> Connect: + return Connect(file_name=file_name, mode=_mode(mode)) + + +def _value(v: Value) -> ScalarValue: + if v is None: + return ScalarValue(null_value=NULL_VALUE) + elif type(v) is bool: + return ScalarValue(bool_value=v) + elif type(v) is int: + return ScalarValue(int_value=v) + elif type(v) is float: + return ScalarValue(double_value=v) + elif type(v) is Decimal: + return ScalarValue(decimal_value=ProtoDecimal(value=str(v))) + elif type(v) is str: + return ScalarValue(str_value=v) + elif type(v) is datetime: + fraction, seconds = math.modf(v.timestamp()) + return ScalarValue(datetime_value=Timestamp(seconds=int(seconds), nanos=int(fraction * 1000000000))) + elif type(v) is date: + return ScalarValue(date_value=Date(year=v.year, month=v.month, day=v.day)) + elif type(v) is time: + return ScalarValue(time_value=Time(hours=v.hour, minutes=v.minute, seconds=v.second, nanos=v.microsecond * 1000)) + elif type(v) is relativedelta: + nv = v.normalized() + return ScalarValue( + interval_value=Interval( + months=12 * nv.years + nv.months, + days=nv.days, + nanos=1000000000 * (nv.hours * 3600 + nv.minutes * 60 + nv.seconds) + 1000 * nv.microseconds, + ) + ) + else: + raise ValueError(f"Invalid type of value: {v} ({type(v)})") + + +def _params(*params: tuple[Value]) -> Params: + return Params(params=[_value(p) for p in params]) + + +def local_file(path: Path) -> Location: + return Location(local=Location.LocalFile(path=str(path))) + + +def execute(query: str, *params: tuple[Value]) -> Query: + return Query(execute=Query.Execute(query=query, params=_params(*params))) + + +def value(query: str, *params: tuple[Value]) -> Query: + return Query(value=Query.QueryValue(query=query, params=_params(*params))) + + +def rows(query: str, *params: tuple[Value]) -> Query: + return Query(rows=Query.QueryRows(query=query, params=_params(*params))) + + +def ctas(table_name: str, query: str, *params: tuple[Value]) -> Query: + return Query(ctas=Query.CreateTableAsQuery(table_name=table_name, query=query, params=_params(*params))) + + +def parquet(location: Location, query: str, *params: tuple[Value]) -> Query: + return Query(parquet=Query.ParquetQuery(location=location, query=query, params=_params(*params))) + + +def request(kind: Connect | Query) -> Request: + if type(kind) is Connect: + return Request(connect=kind) + elif type(kind) is Query: + return Request(query=kind) + else: + raise ValueError(f"unsupported type of message: {kind}") diff --git a/client/gduck/response.py b/client/gduck/response.py new file mode 100644 index 0000000..28acfb3 --- /dev/null +++ b/client/gduck/response.py @@ -0,0 +1,134 @@ +from __future__ import annotations + +from datetime import date, datetime, time +from decimal import Decimal +from pathlib import Path +from typing import Callable + +from database_pb2 import DataType, Rows, ScalarValue +from dateutil.relativedelta import relativedelta +from location_pb2 import Location + +from .types import ParquetLocation, Schema, Value + +__all__ = ["parse_value", "parse_rows", "parse_location"] + + +def _null_value(v: ScalarValue) -> None: + return None + + +def _bool_value(v: ScalarValue) -> bool: + return v.bool_value + + +def _int_value(v: ScalarValue) -> int: + return v.int_value + + +def _uint_value(v: ScalarValue) -> int: + return v.uint_value + + +def _double_value(v: ScalarValue) -> float: + return v.double_value + + +def _decimal_value(v: ScalarValue) -> Decimal: + return Decimal(v.decimal_value.value) + + +def _str_value(v: ScalarValue) -> str: + return v.str_value + + +def _datetime_value(v: ScalarValue) -> datetime: + return v.datetime_value.ToDatetime() + + +def _date_value(v: ScalarValue) -> date: + dt = v.date_value + return date(year=dt.year, month=dt.month, day=dt.day) + + +def _time_value(v: ScalarValue) -> time: + t = v.time_value + return time( + hour=t.hours, minute=t.minutes, second=t.seconds, microsecond=int(t.nanos / 1000) + ) # FIXME python time does not support nanosecond precision + + +def _interval_value(v: ScalarValue) -> relativedelta: + interval = v.interval_value + return relativedelta( + months=interval.months, days=interval.days, microseconds=int(interval.nanos / 1000) + ) # FIXME python relativedelta does not support nanosecond precision + + +def _getter(data_type: DataType) -> Callable[[ScalarValue], Value]: + if data_type == DataType.DATATYPE_NULL: + return _null_value + elif data_type == DataType.DATATYPE_BOOL: + return _bool_value + elif data_type in (DataType.DATATYPE_INT, DataType.DATATYPE_UINT): + return _int_value + elif data_type == DataType.DATATYPE_DOUBLE: + return _double_value + elif data_type == DataType.DATATYPE_DECIMAL: + return _decimal_value + elif data_type == DataType.DATATYPE_STRING: + return _str_value + elif data_type == DataType.DATATYPE_DATETIME: + return _datetime_value + elif data_type == DataType.DATATYPE_DATE: + return _date_value + elif data_type == DataType.DATATYPE_TIME: + return _time_value + elif data_type == DataType.DATATYPE_INTERVAL: + return _interval_value + else: + raise ValueError(f"unknown data type: {data_type}") + + +def parse_value(v: ScalarValue) -> Value: + if v.HasField("null_value"): + return None + elif v.HasField("bool_value"): + return _bool_value(v) + elif v.HasField("int_value"): + return _int_value(v) + elif v.HasField("uint_value"): + return _uint_value(v) + elif v.HasField("double_value"): + return _double_value(v) + elif v.HasField("decimal_value"): + return _decimal_value(v) + elif v.HasField("str_value"): + return _str_value(v) + elif v.HasField("datetime_value"): + return _datetime_value(v) + elif v.HasField("date_value"): + return _date_value(v) + elif v.HasField("time_value"): + return _time_value(v) + elif v.HasField("interval_value"): + return _interval_value(v) + else: + raise ValueError(f"unknown type of value {v}") + + +def parse_rows(rows: Rows) -> tuple[Schema, list[tuple[Value]]]: + schema = Schema.from_proto(rows.schema) + + getters = [_getter(col.data_type) for col in schema] + + ret = [] + for row in rows.rows: + values = tuple(getters[i](v) for i, v in enumerate(row.values)) + ret.append(values) + + return schema, ret + + +def parse_location(location: Location) -> ParquetLocation: + return Path(location.local.path) diff --git a/client/gduck/types.py b/client/gduck/types.py new file mode 100644 index 0000000..9d9d37e --- /dev/null +++ b/client/gduck/types.py @@ -0,0 +1,53 @@ +from dataclasses import dataclass +from datetime import date, datetime, time +from decimal import Decimal +from pathlib import Path +from typing import Iterator, Self, TypeAlias + +from database_pb2 import Column as ProtoColumn +from database_pb2 import DataType +from database_pb2 import Schema as ProtoSchema +from dateutil.relativedelta import relativedelta + +Value: TypeAlias = bool | int | float | Decimal | str | datetime | date | time | relativedelta | None + + +@dataclass(frozen=True) +class Column: + name: str + data_type: DataType + + @classmethod + def from_proto(cls, col: ProtoColumn) -> Self: + return cls(name=col.name, data_type=col.data_type) + + +@dataclass(frozen=True) +class Schema: + columns: list[Column] + + def __iter__(self) -> Iterator[Column]: + return iter(self.columns) + + def __getitem__(self, key: int) -> Column: + return self.columns[key] + + def names(self) -> list[str]: + return [col.name for col in self.columns] + + @classmethod + def from_proto(cls, schema: ProtoSchema) -> Self: + columns = [Column.from_proto(col) for col in schema.columns] + return cls(columns) + + +@dataclass(frozen=True) +class Rows: + schema: Schema + rows: list[tuple[Value]] + + def __iter__(self) -> Iterator[tuple[Value]]: + return iter(self.rows) + + +ParquetLocation: TypeAlias = Path diff --git a/client/main.py b/client/main.py index eb959f6..d22e062 100644 --- a/client/main.py +++ b/client/main.py @@ -1,6 +1,7 @@ -from client import Connection +from gduck.client import Connection if __name__ == "__main__": - with Connection("localhost:50051").transaction(database_file="example.duckdb", mode="read_write") as trans: - result = trans.query("SELECT '1'") - print(result) + with Connection("localhost:50051").transaction(database_file="datasets/example.duckdb", mode="read_write") as trans: + result = trans.query_rows("SELECT * FROM videos WHERE comment_count > ? LIMIT 10;", 2) + for row in result: + print(row) diff --git a/client/request.py b/client/request.py deleted file mode 100644 index fc09c19..0000000 --- a/client/request.py +++ /dev/null @@ -1,57 +0,0 @@ -import math -from datetime import date, datetime, time, timedelta -from decimal import Decimal -from typing import Literal, TypeAlias - -from database_pb2 import Connect, DataType, Date -from database_pb2 import Decimal as ProtoDecimal -from database_pb2 import Interval, Params, Row, Rows, ScalarValue, Schema, Time -from error_pb2 import Error, ErrorCode -from google.protobuf.struct_pb2 import NULL_VALUE -from google.protobuf.timestamp_pb2 import Timestamp -from location_pb2 import Location -from query_pb2 import Query - -__all__ = ["ConnectionMode", "connect"] - -ConnectionMode = Literal["auto", "read_write", "read_only"] -Value: TypeAlias = bool | int | float | Decimal | str | datetime | date | time | timedelta | None - - -def _mode(m: ConnectionMode = Connect.Mode.MODE_AUTO) -> Connect.Mode: - if m == "read_write": - return Connect.Mode.MODE_READ_WRITE - elif m == "read_only": - return Connect.Mode.MODE_READ_ONLY - else: - return Connect.Mode.MODE_AUTO - - -def connect(file_name: str, mode: ConnectionMode) -> Connect: - return Connect(file_name=file_name, mode=_mode(mode)) - - -def _value(v: Value) -> ScalarValue: - if v is None: - return ScalarValue(null_value=NULL_VALUE) - elif type(v) is bool: - return ScalarValue(bool_value=v) - elif type(v) is int: - return ScalarValue(int_value=v) - elif type(v) is float: - return ScalarValue(double_value=v) - elif type(v) is Decimal: - return ScalarValue(decimal_value=ProtoDecimal(value=str(v))) - elif type(v) is str: - return ScalarValue(str_value=v) - elif type(v) is datetime: - fraction, seconds = math.modf(v.timestamp()) - return ScalarValue(datetime_value=Timestamp(seconds=int(seconds), nanos=int(fraction * 1000000000))) - elif type(v) is date: - return ScalarValue(date_value=Date(year=v.year, month=v.month, day=v.day)) - elif type(v) is time: - return ScalarValue(time_value=Time(hours=v.hour, minutes=v.minute, seconds=v.second, nanos=v.microsecond * 1000)) - elif type(v) is timedelta: - pass - else: - raise ValueError(f"Invalid type of value: {v} ({type(v)})")