Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add spice user agent, update for clippy rules #44

Merged
merged 10 commits into from
Sep 19, 2024
Merged
76 changes: 69 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ env:
CARGO_TERM_COLOR: always

jobs:
build_and_test:
build_toolchains:
name: Clippy on ${{ matrix.toolchain }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain:
- stable
- beta
- nightly

steps:
- uses: actions/checkout@v4

Expand All @@ -45,24 +46,85 @@ jobs:
sarif_file: rust-clippy-results.sarif
wait-for-processing: true

- name: Build
run: cargo build --verbose
build_and_test:
name: Build and test ${{matrix.os}} on ${{ matrix.toolchain }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
toolchain:
- stable
- beta
steps:
- uses: actions/checkout@v4

- name: install Spice (https://install.spiceai.org)
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
components: clippy
override: true

- name: Cargo clippy
run: cargo clippy --all-features

- name: Install Spice (https://install.spiceai.org) (Linux)
if: matrix.os == 'ubuntu-latest'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
curl https://install.spiceai.org | /bin/bash
echo "$HOME/.spice/bin" >> $GITHUB_PATH
$HOME/.spice/bin/spice install

- name: Install Spice (https://install.spiceai.org) (MacOS)
if: matrix.os == 'macos-latest'
run: |
brew install spiceai/spiceai/spice
brew install spiceai/spiceai/spiced

- name: install Spice (Windows)
if: matrix.os == 'windows-latest'
run: |
curl -L "https://install.spiceai.org/Install.ps1" -o Install.ps1 && PowerShell -ExecutionPolicy Bypass -File ./Install.ps1

- name: add Spice bin to PATH (Windows)
if: matrix.os == 'windows-latest'
run: |
Add-Content $env:GITHUB_PATH (Join-Path $HOME ".spice\bin")
shell: pwsh

- name: Init and start spice app
if: matrix.os != 'windows-latest'
run: |
spice init spice_qs
cd spice_qs
spice add spiceai/quickstart
spice run &> spice.log &
spiced &> spice.log &
# time to initialize added dataset
sleep 10

- name: Init and start spice app (Windows)
if: matrix.os == 'windows-latest'
run: |
spice init spice_qs
cd spice_qs
spice add spiceai/quickstart
Start-Process -FilePath spice run
# time to initialize added dataset
Start-Sleep -Seconds 10
shell: pwsh

- name: Run tests
run: cargo test --verbose
run: cargo test
env:
API_KEY: ${{ secrets.TEST_API_KEY }}

- name: Stop spice and check logs
working-directory: spice_qs
if: matrix.os != 'windows-latest' && always()
run: |
killall spice || true
cat spice.log
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ jobs:
toolchain: stable
override: true

- run: cargo build --verbose
- run: cargo build

- run: cargo publish --verbose
- run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ dotenv = "0.15.0"
arrow = "51.0.0"
futures = "0.3.30"
base64 = "0.22.0"

[target.'cfg(windows)'.dependencies]
winver = "1.0.0"

[dev-dependencies]
regex = "1.10.6"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async fn main() {
}
```

### New client with https://spice.ai cloud
### New client with <https://spice.ai> cloud

```rust
use spiceai::ClientBuilder;
Expand Down
37 changes: 30 additions & 7 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::{
config::{SPICE_CLOUD_FIRECACHE_ADDR, SPICE_CLOUD_FLIGHT_ADDR, SPICE_LOCAL_FLIGHT_ADDR},
config::{
GenericError, SPICE_CLOUD_FIRECACHE_ADDR, SPICE_CLOUD_FLIGHT_ADDR, SPICE_LOCAL_FLIGHT_ADDR,
},
flight::SqlFlightClient,
tls::new_tls_flight_channel,
};
use arrow_flight::decode::FlightRecordBatchStream;
use futures::try_join;
use std::error::Error;
use tonic::transport::Channel;

struct SpiceClientConfig {
Expand All @@ -21,7 +22,7 @@ impl SpiceClientConfig {
}
}

pub async fn load_from_default() -> Result<SpiceClientConfig, Box<dyn Error>> {
pub async fn load_from_default() -> Result<SpiceClientConfig, GenericError> {
let (flight_chan, firecache_chan) = try_join!(
new_tls_flight_channel(SPICE_CLOUD_FLIGHT_ADDR),
new_tls_flight_channel(SPICE_CLOUD_FIRECACHE_ADDR)
Expand Down Expand Up @@ -49,7 +50,11 @@ impl SpiceClient {
/// let mut client = Client::new("API_KEY").await.unwrap();
/// }
/// ```
pub async fn new(api_key: &str) -> Result<Self, Box<dyn Error>> {
///
/// ## Errors
///
/// - `Box<dyn Error + Send + Sync>` for any query error
pub async fn new(api_key: &str) -> Result<Self, GenericError> {
let config = SpiceClientConfig::load_from_default().await?;

Ok(Self {
Expand All @@ -58,6 +63,7 @@ impl SpiceClient {
})
}

#[must_use]
pub fn builder() -> SpiceClientBuilder {
SpiceClientBuilder::new()
}
Expand All @@ -72,7 +78,11 @@ impl SpiceClient {
/// let data = client.query("SELECT * FROM eth.recent_blocks LIMIT 10;").await;
/// # }
/// ````
pub async fn query(&mut self, query: &str) -> Result<FlightRecordBatchStream, Box<dyn Error>> {
///
/// ## Errors
///
/// - `Box<dyn Error + Send + Sync>` for any query error
pub async fn query(&mut self, query: &str) -> Result<FlightRecordBatchStream, GenericError> {
self.flight.query(query).await
}

Expand All @@ -86,10 +96,14 @@ impl SpiceClient {
/// let data = client.fire_query("SELECT * FROM eth.recent_blocks LIMIT 10;").await;
/// # }
/// ````
///
/// ## Errors
///
/// - `Box<dyn Error + Send + Sync>` for any query error
pub async fn fire_query(
&mut self,
query: &str,
) -> Result<FlightRecordBatchStream, Box<dyn Error>> {
) -> Result<FlightRecordBatchStream, GenericError> {
self.firecache.query(query).await
}
}
Expand Down Expand Up @@ -138,6 +152,7 @@ impl Default for SpiceClientBuilder {
}

impl SpiceClientBuilder {
#[must_use]
pub fn new() -> Self {
Self {
api_key: None,
Expand All @@ -147,33 +162,41 @@ impl SpiceClientBuilder {
}

/// Configures the `SpiceClient` to use the given API key.
#[must_use]
pub fn api_key(mut self, api_key: &str) -> Self {
self.api_key = Some(api_key.to_string());
self
}

/// Configures the `SpiceClient` to use the given Spice Firecache endpoint.
#[must_use]
pub fn firecache_url(mut self, firecache_url: &str) -> Self {
self.firecache_url = Some(firecache_url.to_string());
self
}

/// Configures the `SpiceClient` to use the given Spice Flight endpoint.
#[must_use]
pub fn flight_url(mut self, flight_url: &str) -> Self {
self.flight_url = Some(flight_url.to_string());
self
}

/// Configures the `SpiceClient` to use default Spice.ai Cloud endpoints.
/// Equivalent to calling `.firecache_url("https://firecache.spiceai.io")` and `.flight_url("https://flight.spiceai.io")`.
#[must_use]
pub fn use_spiceai_cloud(mut self) -> Self {
self.flight_url = Some(SPICE_CLOUD_FLIGHT_ADDR.to_string());
self.firecache_url = Some(SPICE_CLOUD_FIRECACHE_ADDR.to_string());
self
}

/// Builds the `SpiceClient` with the specified configuration.
pub async fn build(self) -> Result<SpiceClient, Box<dyn Error>> {
///
/// ## Errors
///
/// - `Box<dyn Error + Send + Sync>` if flight or firecache channel creation fails
pub async fn build(self) -> Result<SpiceClient, GenericError> {
let flight_channel = match self.flight_url {
Some(url) => new_tls_flight_channel(&url).await?,
None => new_tls_flight_channel(SPICE_LOCAL_FLIGHT_ADDR).await?,
Expand Down
77 changes: 77 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,80 @@ pub const SPICE_CLOUD_FIRECACHE_ADDR: &str = "https://firecache.spiceai.io";

// default address for local spice runtime
pub const SPICE_LOCAL_FLIGHT_ADDR: &str = "http://localhost:50051";

pub type GenericError = Box<dyn std::error::Error + Send + Sync>;

#[cfg(target_family = "unix")]
fn get_os_release() -> Result<String, GenericError> {
// call uname -r to get release text
use std::process::Command;
let output = Command::new("uname").arg("-r").output()?;
let release = String::from_utf8(output.stdout)?;

Ok(release)
}

#[cfg(target_family = "windows")]
fn get_os_release() -> Result<String, GenericError> {
use winver::WindowsVersion;
if let Some(version) = WindowsVersion::detect() {
Ok(version.to_string())
} else {
Ok("unknown".to_string())
}
}

pub(crate) fn get_user_agent() -> String {
let os_type = std::env::consts::OS;
let os_type = match os_type {
"" => "unknown".to_string(),
"macos" => "Darwin".to_string(),
"linux" => "Linux".to_string(),
"windows" => "Windows".to_string(),
"ios" => "iOS".to_string(),
"android" => "Android".to_string(),
"freebsd" => "FreeBSD".to_string(),
"dragonfly" => "DragonFlyBSD".to_string(),
"netbsd" => "NetBSD".to_string(),
"openbsd" => "OpenBSD".to_string(),
"solaris" => "Solaris".to_string(),
_ => os_type.to_string(),
};

let os_arch = std::env::consts::ARCH;
let os_arch = match os_arch {
"" => "unknown".to_string(),
"x86" => "i386".to_string(),
_ => os_arch.to_string(),
};

let os_release = get_os_release()
.unwrap_or_else(|_| "unknown".to_string())
.trim()
.to_string();

format!(
"spice-rs {} ({os_type}/{os_release} {os_arch})",
env!("CARGO_PKG_VERSION")
)
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_get_user_agent() {
let matching_regex = regex::Regex::new(
r"spice-rs \d+\.\d+\.\d+ \((Linux|Windows|Darwin)/[\d\w\.\-\_]+ (x86_64|aarch64|i386)\)",
)
.expect("regex should be constructed");

let user_agent = get_user_agent();
let agent_matches = matching_regex.is_match(&user_agent);
assert!(
agent_matches,
"expected user agent to match regex, but got {user_agent}"
);
}
}
Loading
Loading