Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Set up Pixi
uses: prefix-dev/setup-pixi@main
with:
pixi-version: latest
pixi-version: v0.70.2
cache: true

- name: Try Building The Package
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ jobs:

- uses: prefix-dev/setup-pixi@v0.9.3
with:
pixi-version: v0.59.0
pixi-version: v0.70.2
cache: true

- name: Run tests
shell: bash
Expand Down
83 changes: 59 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,26 @@ A `requests` like HTTP client for Mojo, leveraging `libcurl` under the hood.
![Test Status](https://github.com/thatstoasty/floki/actions/workflows/test.yml/badge.svg)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Adding the `floki` package to your project
```mojo
import floki

### Installing it from the `mojo-community` Conda channel
def main() raises -> None:
var response = floki.get("https://example.com")
for pair in response.headers.items():
print(pair.key, ": ", pair.value)
print(response.as_text())
```

First, you'll need to install the `curl_wrapper` library, which provides a thin wrapper around libcurl to avoid issues with variadic arguments. You'll need to enable the `pixi-build` preview by adding this to the `workspace section of your `pixi.toml` file.
## Adding the `floki` package to your project

```bash
preview = ["pixi-build"]
```
### Pre-requisites

Next, you can add `curl_wrapper` by running:
You'll need to enable the `pixi-build` preview by adding this to the workspace section of your `pixi.toml` file.

```bash
pixi add curl_wrapper -g "https://github.com/thatstoasty/mojo-curl.git" --subdir shim --tag v0.3.1
preview = ["pixi-build"]
```

> Note: Mojo cannot currently support calling C functions with variadic arguments, and the libcurl client interface makes heavy use of them. The `curl_wrapper` library provides a thin wrapper around libcurl to avoid this issue. Remember to always validate the code you're pulling from third-party sources!

### Building it from source

There's two ways to build `floki` from source: directly from the Git repository or by cloning the repository locally.
Expand All @@ -34,7 +36,7 @@ There's two ways to build `floki` from source: directly from the Git repository
Run the following commands in your terminal:

```bash
pixi add -g "https://github.com/thatstoasty/floki.git" --tag v0.3.3 && pixi install
pixi add floki --git "https://github.com/thatstoasty/floki.git" --tag v0.3.4 && pixi install
```

#### Building from source: Local
Expand All @@ -47,16 +49,47 @@ git clone https://github.com/thatstoasty/floki.git
pixi add -s ./path/to/floki && pixi install
```

```mojo
import floki
## Configuring library paths

def main() raises -> None:
var response = floki.get("https://example.com")
for pair in response.headers.items():
print(pair.key, ": ", pair.value)
print(response.text())
> Note: Mojo cannot currently support calling C functions with variadic arguments, and the libcurl client interface makes heavy use of them. Floki uses my small shim C library `curl_wrapper`, which provides a thin wrapper around libcurl to avoid this issue.

Floki leverages `mojo-curl` as an FFI interface to `libcurl`, and it needs to locate two dynamic libraries at runtime: `libcurl` and `libcurl_wrapper` (the thin C shim that wraps libcurl's variadic functions).

### Default behavior

By default, the library looks for both in your project's Pixi environment:

| Library | macOS | Linux |
| --- | --- | --- |
| libcurl | `.pixi/envs/default/lib/libcurl.dylib` | `.pixi/envs/default/lib/libcurl.so` |
| curl_wrapper | `.pixi/envs/default/lib/libcurl_wrapper.dylib` | `.pixi/envs/default/lib/libcurl_wrapper.so` |

If you're working in a Pixi environment, the libraries will already be in the expected location and no additional configuration is needed.

### Custom library paths

If your libraries are in a different location, you can override the paths using either **environment variables** or **compile-time defines**.

#### Environment variables

Set `LIBCURL_LIB_PATH` and `CURL_WRAPPER_LIB_PATH` before running your program:

```bash
export LIBCURL_LIB_PATH="/usr/lib/libcurl.so"
export CURL_WRAPPER_LIB_PATH="/opt/mylibs/libcurl_wrapper.so"
mojo run my_program.mojo
```

#### Compile-time defines

Pass the paths as `-D` flags when compiling or running:

```bash
mojo run -D LIBCURL_LIB_PATH="/usr/lib/libcurl.so" -D CURL_WRAPPER_LIB_PATH="/opt/mylibs/libcurl_wrapper.so" my_program.mojo
```

Compile-time defines take priority over environment variables. If neither is set, the default Pixi environment paths are used.

## Features

Floki aims to provide a `requests`-like experience on top of `libcurl`.
Expand Down Expand Up @@ -122,15 +155,15 @@ def main() raises -> None:
r.raise_for_status() # raises HTTPError on a non-2xx response

# Body as text, raw bytes, or JSON.
print(r.text()) # StringSlice over the body
var data = r.json() # dynamic JSON, e.g. data["url"]
print(r.as_text()) # StringSlice over the body
var data = r.as_json() # dynamic JSON, e.g. data["url"]

# Headers are looked up case-insensitively.
print(r.content_type()) # value of the Content-Type header
print(r.header("x-request-id", "<none>"))
```

For typed JSON, deserialize straight into a struct with `r.as_json[T]()`:
For typed JSON, deserialize straight into a struct with `r.as[T]()`:

```mojo
import floki
Expand All @@ -146,7 +179,7 @@ struct Todo(Defaultable, ImplicitlyDestructible, Movable):

def main() raises -> None:
var r = floki.get("https://jsonplaceholder.typicode.com/todos/1")
var todo = r.as_json[Todo]()
var todo = r.as[Todo]()
print(todo.id, todo.title)
```

Expand All @@ -160,6 +193,7 @@ can supply its own headers. Authentication is provided per request:

```mojo
import floki
from floki import Headers
from floki.auth import Auth, BasicAuth, BearerAuth

def main() raises -> None:
Expand All @@ -171,7 +205,7 @@ def main() raises -> None:
struct ApiKeyAuth(Auth):
var key: String

def apply(self, mut headers: Dict[String, String]):
def apply(self, mut headers: Headers):
headers["X-Api-Key"] = self.key
```

Expand Down Expand Up @@ -245,6 +279,7 @@ TLS certificate and hostname verification is enabled by default. It can be disab
(use with great caution) or pointed at a custom certificate authority bundle:

```mojo
from std.pathlib import Path
from floki.session import Session
from floki.tls import TLS

Expand All @@ -253,7 +288,7 @@ def main() raises -> None:
var insecure = Session(tls=TLS(verify=False))

# Use a custom CA bundle (e.g. a private PKI or self-signed cert).
var custom = Session(tls=TLS(ca_bundle="/path/to/ca-bundle.pem"))
var custom = Session(tls=TLS(ca_bundle=Path("/path/to/ca-bundle.pem")))
var r = custom.get("https://internal.example.com")
```

Expand Down
8 changes: 8 additions & 0 deletions examples/mocurl/main.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from floki import Session
from prism import Command

def main() raises:
var session = Session()
var response = session.get("http://localhost:8080/")
print(response.as_text())

Loading
Loading