Skip to content

Commit

Permalink
Merge pull request #2754 from finos/python-imports
Browse files Browse the repository at this point in the history
Python API changes from community
  • Loading branch information
texodus authored Sep 22, 2024
2 parents 0469dda + 61edb84 commit 1f8a10f
Show file tree
Hide file tree
Showing 50 changed files with 1,021 additions and 980 deletions.
11 changes: 8 additions & 3 deletions cmake/modules/FindInstallDependency.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,16 @@ function(psp_build_dep name cmake_file)
set(ARROW_LZ4 ON)
set(ARROW_WITH_ZSTD ON)
set(ARROW_WITH_LZ4 ON)
set(ARROW_ENABLE_THREADING OFF)
set(ARROW_NO_EXPORT ON)
if(WIN32 AND NOT PSP_BUILD_WASM)
set(ARROW_DEPENDENCY_SOURCE "BUNDLED")
if(PSP_WASM_BUILD)
set(ARROW_ENABLE_THREADING OFF)
else()
set(ARROW_ENABLE_THREADING ON)
if(WIN32)
set(ARROW_DEPENDENCY_SOURCE "BUNDLED")
endif()
endif()

include_directories(SYSTEM ${CMAKE_BINARY_DIR}/${name}-build/src)
add_subdirectory(${CMAKE_BINARY_DIR}/${name}-src/cpp/
${CMAKE_BINARY_DIR}/${name}-build
Expand Down
11 changes: 5 additions & 6 deletions cpp/perspective/src/cpp/arrow_csv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -609,12 +609,11 @@ csvToTable(
auto read_options = arrow::csv::ReadOptions::Defaults();
auto parse_options = arrow::csv::ParseOptions::Defaults();
auto convert_options = arrow::csv::ConvertOptions::Defaults();
// #ifdef PSP_PARALLEL_FOR
// read_options.use_threads = true;
// #else
// read_options.use_threads = false;
// #endif
read_options.use_threads = false;
#ifdef PSP_PARALLEL_FOR
read_options.use_threads = true;
#else
read_options.use_threads = false;
#endif
parse_options.newlines_in_values = true;

if (is_update) {
Expand Down
4 changes: 2 additions & 2 deletions cpp/perspective/src/cpp/table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1082,8 +1082,8 @@ Table::from_cols(

if (is_implicit) {
for (t_uindex ii = 0; ii < nrows; ii++) {
psp_pkey_col->set_nth<std::int32_t>(ii, ii);
psp_okey_col->set_nth<std::int32_t>(ii, ii);
psp_pkey_col->set_nth<std::int32_t>(ii, ii % limit);
psp_okey_col->set_nth<std::int32_t>(ii, ii % limit);
}
}

Expand Down
12 changes: 6 additions & 6 deletions cpp/perspective/src/cpp/view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1211,12 +1211,12 @@ View<CTX_T>::data_slice_to_arrow(
options.codec = std::move(codec).ValueUnsafe();
}

// #ifdef PSP_PARALLEL_FOR
// options.use_threads = false;
// #else
// options.use_threads = false;
// #endif
options.use_threads = false;
#ifdef PSP_PARALLEL_FOR
options.use_threads = true;
#else
options.use_threads = false;
#endif

auto res = arrow::ipc::MakeStreamWriter(&sink, arrow_schema, options);
std::shared_ptr<arrow::ipc::RecordBatchWriter> writer = *res;
PSP_CHECK_ARROW_STATUS(writer->WriteRecordBatch(*batches));
Expand Down
2 changes: 1 addition & 1 deletion examples/python-tornado-streaming/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def make_app(perspective_server):
[
(
r"/websocket",
perspective.PerspectiveTornadoHandler,
perspective.handlers.tornado.PerspectiveTornadoHandler,
{"perspective_server": perspective_server},
),
(
Expand Down
1 change: 1 addition & 0 deletions examples/python-tornado/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import tornado.ioloop

import perspective
import perspective.handlers.tornado

here = os.path.abspath(os.path.dirname(__file__))
file_path = os.path.join(
Expand Down
3 changes: 1 addition & 2 deletions examples/rust-axum/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use axum::routing::get_service;
use axum::Router;
use perspective::client::{TableInitOptions, UpdateData};
use perspective::server::Server;
use perspective::LocalClient;
use tower_http::services::{ServeDir, ServeFile};
use tower_http::trace::TraceLayer;
use tracing_subscriber::filter::LevelFilter;
Expand All @@ -36,7 +35,7 @@ type AppError = Box<dyn std::error::Error + Send + Sync>;
/// Load the example Apache Arrow file from disk and create a
/// [`perspective::Table`] named "my_data_source".
async fn load_server_arrow(server: &Server) -> Result<(), AppError> {
let client = LocalClient::new(server);
let client = server.new_local_client();
let mut file = File::open(std::path::Path::new(ROOT_PATH).join(ARROW_FILE_PATH))?;
let mut feather = Vec::with_capacity(file.metadata()?.len() as usize);
file.read_to_end(&mut feather)?;
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rust/bootstrap/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fn main() {
.args(debug_flags)
.env(
"BOOTSTRAP_TARGET",
&fs::canonicalize(args.input.clone()).unwrap(),
fs::canonicalize(args.input.clone()).unwrap(),
)
.execute();

Expand Down
11 changes: 11 additions & 0 deletions rust/perspective-python/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import * as fs from "node:fs";
import sh from "../../tools/perspective-scripts/sh.mjs";
import * as url from "url";
import * as TOML from "@iarna/toml";

const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1);
const pkg = JSON.parse(
Expand Down Expand Up @@ -89,7 +90,13 @@ if (build_wheel) {
cmd.sh(`maturin build ${flags} --features=external-cpp ${target}`);
}

const old = fs.readFileSync("./pyproject.toml");

if (build_sdist) {
const toml = TOML.parse(old);
console.log(toml);
delete toml.tool.maturin["data"];
fs.writeFileSync("./pyproject.toml", TOML.stringify(toml));
cmd.sh(`maturin sdist`);
}

Expand All @@ -98,3 +105,7 @@ if (!build_wheel && !build_sdist) {
}

cmd.runSync();

if (build_sdist) {
fs.writeFileSync("./pyproject.toml", old);
}
47 changes: 20 additions & 27 deletions rust/perspective-python/docs/lib.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,26 +51,23 @@ makes it simple to extend a Tornado server with virtual Perspective support.

The `perspective` module exports several tools:

- `PerspectiveWidget` the JupyterLab widget for interactive visualization.
- Perspective webserver handlers that interface seamlessly with
`<perspective-viewer>` in JavaScript.
- `PerspectiveTornadoHandler` for [Tornado](https://www.tornadoweb.org/)
- `PerspectiveStarletteHandler` for [Starlette](https://www.starlette.io/)
and [FastAPI](https://fastapi.tiangolo.com)
- `PerspectiveAIOHTTPHandler` for [AIOHTTP](https://docs.aiohttp.org),
- `tornado_websocket`, a Tornado-based websocket client
- `aiohttp_websocket` an AIOHTTP-based websocket client
- `Server` the session manager for a shared server deployment of
`perspective-python`.
- `Server` the constructor for a new isntance of the Perspective data engine.
- The `perspective.widget` module exports `PerspectiveWidget`, the JupyterLab
widget for interactive visualization in a notebook cell.
- The `perspective.handlers` modules exports web frameworks handlers that
interface with a `perspective-client` in JavaScript.
- `perspective.handlers.tornado.PerspectiveTornadoHandler` for
[Tornado](https://www.tornadoweb.org/)
- `perspective.handlers.starlette.PerspectiveStarletteHandler` for
[Starlette](https://www.starlette.io/) and
[FastAPI](https://fastapi.tiangolo.com)
- `perspective.handlers.aiohttp.PerspectiveAIOHTTPHandler` for
[AIOHTTP](https://docs.aiohttp.org),

This user's guide provides an overview of the most common ways to use
Perspective in Python: the `Table` API, the JupyterLab widget, and the Tornado
handler.

For an understanding of Perspective's core concepts, see the [Table](table.md),
[View](view.md), and [Data Binding](server.md) documentation. For API
documentation, see the [Python API](obj/perspective-python.md).

[More Examples](https://github.com/finos/perspective/tree/master/examples) are
available on GitHub.

Expand All @@ -80,10 +77,6 @@ available on GitHub.
widget, and a WebSocket handlers for several webserver libraries that allow you
to host Perspective using server-side Python.

In addition to supporting row/columnar formats of data using `dict` and `list`,
`pandas.DataFrame`, dictionaries of NumPy arrays, NumPy structured arrays, and
NumPy record arrays are all supported in `perspective-python`.

### PyPI

`perspective-python` can be installed from [PyPI](https://pypi.org) via `pip`:
Expand Down Expand Up @@ -146,15 +139,15 @@ data = pd.DataFrame({
"string": [str(i) for i in range(100)]
})

table = perspective.Table(data, index="float")
table = perspective.table(data, index="float")
```

Likewise, a `View` can be created via the `view()` method:

```python
view = table.view(group_by=["float"], filter=[["bool", "==", True]])
column_data = view.to_dict()
row_data = view.to_records()
column_data = view.to_columns()
row_data = view.to_json()
```

### Pandas Support
Expand Down Expand Up @@ -198,14 +191,14 @@ def update_callback():
print("Updated!")

# set the update callback
view.on_update(update_callback)
on_update_id = view.on_update(update_callback)


def delete_callback():
print("Deleted!")

# set the delete callback
view.on_delete(delete_callback)
on_delete_id = view.on_delete(delete_callback)

# set a lambda as a callback
view.on_delete(lambda: print("Deleted x2!"))
Expand All @@ -215,16 +208,16 @@ If the callback is a named reference to a function, it can be removed with
`remove_update` or `remove_delete`:

```python
view.remove_update(update_callback)
view.remove_delete(delete_callback)
view.remove_update(on_update_id)
view.remove_delete(on_delete_id)
```

Callbacks defined with a lambda function cannot be removed, as lambda functions
have no identifier.

#### Multi-threading

Perspective's server is completely releases the GIL (though it may be retained
Perspective's server API releases the GIL when called (though it may be retained
for some portion of the `Client` call to encode RPC messages). It also
dispatches to an internal thread pool for some operations, enabling better
parallelism and overall better server performance. However, Perspective's Python
Expand Down
1 change: 1 addition & 0 deletions rust/perspective-python/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"devDependencies": {
"@finos/perspective-scripts": "workspace:*",
"@iarna/toml": "^1.0.0-rc.1",
"cpy": "^9.0.1"
},
"dependencies": {
Expand Down
71 changes: 17 additions & 54 deletions rust/perspective-python/perspective/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,77 +13,40 @@
__version__ = "3.0.3"
__all__ = [
"_jupyter_labextension_paths",
"Server",
"Client",
"PerspectiveError",
"PerspectiveWidget",
"PerspectiveViewer",
"PerspectiveTornadoHandler",
"ProxySession",
"Table",
"View",
"Server",
"Client",
]

import functools

from .perspective import (
Client as PySyncClient,
Client,
PerspectiveError,
Table,
View,
ProxySession,
PySyncServer,
PySyncServer as Server,
)

try:
from .widget import PerspectiveWidget
except ImportError:
...
try:
from .viewer import PerspectiveViewer
except ImportError:
...

# from .psp_cffi import ServerBase

try:
from .handlers import PerspectiveTornadoHandler
except ImportError:
...


def default_loop_cb(fn, *args, **kwargs):
return fn(*args, **kwargs)


class Server(PySyncServer):
def set_threadpool_size(self, n_cpus):
pass

def new_local_client(self, loop_callback=default_loop_cb):
"""Create a new `Client` instance bound to this in-process `Server`."""
return Client.from_server(self, loop_callback)
GLOBAL_SERVER = Server()
GLOBAL_CLIENT = GLOBAL_SERVER.new_local_client()


class Client(PySyncClient):
def from_server(
server: Server,
loop_callback=default_loop_cb,
):
"""Create a new `Client` instance bound synchronously to an Python
instance of `PerspectiveServer`."""
@functools.wraps(Client.table)
def table(*args, **kwargs):
return GLOBAL_CLIENT.table(*args, **kwargs)

def handle_request(bytes):
session.handle_request(bytes)
loop_callback(lambda: session.poll())

def handle_response(bytes):
client.handle_response(bytes)
@functools.wraps(Client.open_table)
def open_table(*args, **kwargs):
return GLOBAL_CLIENT.table(*args, **kwargs)

def handle_close():
session.close()

session = server.new_session(handle_response)
client = Client(handle_request, handle_close)
return client
@functools.wraps(Client.get_hosted_table_names)
def get_hosted_table_names(*args, **kwargs):
return GLOBAL_CLIENT.get_hosted_table_names(*args, **kwargs)


# Read by `jupyter labextension develop`
Expand Down
15 changes: 0 additions & 15 deletions rust/perspective-python/perspective/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,3 @@
# ┃ This file is part of the Perspective library, distributed under the terms ┃
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

try:
from .aiohttp import PerspectiveAIOHTTPHandler # noqa: F401
except ImportError:
...

try:
from .starlette import PerspectiveStarletteHandler # noqa: F401
except ImportError:
...

try:
from .tornado import PerspectiveTornadoHandler # noqa: F401
except ImportError:
...
Loading

0 comments on commit 1f8a10f

Please sign in to comment.