# Why should you use `lakefs-spec`?

There are a few reasons that come to mind:

## File system operations `>>` client interactions

Before, the only way to interface with a lakeFS instance was to use the Python API client. While it has its merits,
and (mostly) works as advertised, it has its issues - the developer experience being the most difficult one, with incomplete
typing information, and the woes of reading lots of raw client code, for example when downloading a file to load into a data frame:

```python
from lakefs_sdk import Configuration
from lakefs_sdk.client import LakeFSClient

# painful!
configuration = Configuration(host="my-host")
client = LakeFSClient(configuration=configuration)

repository = "my-repo"
ref = "my-ref"
data = "data.parquet"

with open("res.parquet", "w") as f:
    res = client.objects_api.get_object(repository, ref, data)
    while True:
        chunk = res.read(2 ** 20)
        if not chunk:
            break
        f.write(chunk)
```

When comparing this to `lakefs-spec`, we see that approaching the problem from a file system point of view makes things much easier:

```python
from lakefs_spec import LakeFSFileSystem

fs = LakeFSFileSystem(host="my-host")

fs.get("my-repo/my-ref/data.parquet", "res.parquet")
```

These abstractions make the code needed to interact with files and specific versions of resources much more straightforward.

## Integration with `fsspec`-powered Python data science libraries

Some projects, including `pandas`, `polars`, and `pyspark`, use `fsspec` to abstract I/O into a file-system paradigm.
Registering other file systems is as easy as running a hook in `setuptools` (or `poetry`, depending on your preference)
at build time.

`lakefs-spec` itself is registered as the default file system for the `lakefs` protocol in the official `fsspec` registry,
so all you have to do is to `pip install --upgrade lakefs-spec` to start using it.

With it, reading and writing data in a versioned manner is supported natively, for example in data frame I/O in `pandas`:

```python
import pandas as pd

storage_options={
    "host": "localhost:8000",
    "username": "username",
    "password": "password",
}

df = pd.read_parquet('lakefs://quickstart/main/lakes.parquet', storage_options=storage_options)
```

With more advanced methods in `lakefs-spec` such as config file instantiation and environment variable setting,
you can even instantiate the file system without any arguments and rely on automated client configuration and setup.

## Setting up lakeFS automations and versioning workflows using transactions

As described earlier, dealing with the raw lakeFS API client in interactions with a lakeFS instance can be cumbersome, for reasons like
weirdnesses in autogenerated classes and docstrings, API models instead of raw Python types, and unintuitive keyword
arguments.

Using `lakefs-spec` you can interact with your lakeFS instance in a file-centric way. Each file operation is mapped to
one (or more) client API calls, which are completely abstracted away from the user and hidden behind the standard `fsspec`
APIs, which allow for accurate typing.

Since lakeFS is a data versioning tool and not only an object store, you should be able to carry out its versioning operations _during_
file operations. `lakefs-spec` solves this by defining a `Transaction` class which works as a context manager and has many data versioning operations available.

A concrete example of this is creating lakeFS commits after a successful file upload.
Commits provide a snapshot of a lakeFS repository, similarly to version control systems like git.

```python
from lakefs_spec import LakeFSFileSystem

fs = LakeFSFileSystem()

with fs.transaction as tx:
    # creates a commit with the message "Add file my-file.txt" after the file put.
    # setting `autocommit=False` defers the actual upload until the transaction has finished.
    fs.put_file("my-file.txt", "my-repo/my-branch/my-file.txt", autocommit=False)
    tx.commit("my-repo", "my-branch", message="Add file my-file.txt")
```

The `lakefs_spec.Transaction` class contains multiple useful versioning helpers like `commit`, `tag`, `merge`, and `revert`.
If you need more extensibility, you can define your own subclass of this transaction class to gain even more control over
your versioning workflow.
