Skip to content
This repository has been archived by the owner on Jul 31, 2024. It is now read-only.

Commit

Permalink
feat: initial commit for qsv-duct, v0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
rzmk committed Jun 2, 2024
0 parents commit 8e6b113
Show file tree
Hide file tree
Showing 14 changed files with 34,678 additions and 0 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Publish qsv-duct distribution to PyPI

on:
release:
types: [created]

jobs:
build:
name: Build distribution
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install pypa/build
run: >-
python3 -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: python3 -m build
- name: Store the distribution packages
uses: actions/upload-artifact@v3
with:
name: python-package-distributions
path: dist/

publish-to-pypi:
name: Publish qsv-duct distribution to PyPI
needs:
- build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/qsv-duct # Replace <package-name> with your PyPI project name
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing

steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: python-package-distributions
path: dist/
- name: Publish qsv-duct distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__
.pytest_cache
.venv
dist
99 changes: 99 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# qsv-duct

Python wrapper you may use to call [qsv](https://github.com/jqnatividad/qsv) commands using [duct.py](https://github.com/oconnor663/duct.py) for composability.

This library is compatible with qsv v0.128.0. Not all commands are available.

**Make sure you have qsv installed on your system first and can access it anywhere as a `PATH` command.**

To install this library run:

```bash
pip install qsv-duct
```

## Basic example

We have a file `fruits.csv` with the following contents:

```csv
fruit,price
apple,2.50
banana,3.00
carrot,1.50
```

Let's count the total number of non-header rows in `fruits.csv` using `qsv.count`:

```python
import qsv

qsv.count("fruits.csv", run=True)
```

The following output gets printed to stdout:

```console
3
```

## Reading output to a variable

You may want to save the output value to a variable and use it in your code. Use the `read` parameter instead of `run`. For example:

```python
non_header_row_count = qsv.count("fruits.csv", read=True)
print(non_header_row_count)
```

## Piping commands

Since this library uses duct.py, you may access the command as an `Expression` type by not passing `read` and `run`.

For example, let's say we want to get the first two rows of `fruits.csv` with `qsv.slice`. Normally we would use `run` to run the command:

```python
qsv.slice("fruits.csv", length=2, run=True)
```

```console
fruit,price
apple,2.50
banana,3.00
```

If we want to display this output in a pretty format, we can pipe `qsv.slice` into `qsv.table`:

```python
qsv.slice("fruits.csv", length=2).pipe(qsv.table()).run()
```

```console
fruit price
apple 2.50
banana 3.00
```

If you use the `duct.py` library you can also pipe qsv commands with other bash-related commands using the `duct` library's `cmd` function. For example:

```bash
import qsv
from duct import cmd

cmd("cat", "fruits.csv").pipe(qsv.table()).run()
```

```console
fruit price
apple 2.50
banana 3.00
carrot 1.50
```

## Testing

You can run the tests with the pytest package:

```bash
pytest
```
25 changes: 25 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "qsv-duct"
authors = [{name = "Mueez Khan"}]
readme = "README.md"
dynamic = ["version", "description"]
keywords = ["qsv", "csv"]
dependencies = ["duct"]

[tool.flit.module]
name = "qsv"

[project.urls]
Home = "https://github.com/rzmk/qsv-duct"

[project.optional-dependencies]
test = [
"pytest"
]

[tool.flit.sdist]
exclude = ["**/__pycache__", ".venv"]
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
duct
8 changes: 8 additions & 0 deletions src/qsv/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Python wrapper based on the qsv CLI tool"""

__version__ = "0.0.1"

from .count import count, CountBuilder
from .sample import sample
from .slice import slice
from .table import table
139 changes: 139 additions & 0 deletions src/qsv/count.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from duct import cmd, Expression


def count(
file_path: str = "-",
run: bool = False,
read: bool = False,
include_header_row: bool = False,
human_readable: bool = False,
width: bool = False,
):
"""
# qsv count
Returns a count of the number of records in the CSV data.
Note that the count will not include the header row
unless the `include_header_row` parameter/method is used.
## Examples
Assume we have a file `fruits.csv` with the following content:
```csv
fruit,price
apple,2.50
banana,3.00
carrot,1.50
```
### Get non-header row count and print to stdout
```python
qsv.count("fruits.csv").run()
# or
qsv.count("fruits.csv", run=True)
```
Output:
```console
3
```
### Get non-header row count and read to variable
```python
row_count = qsv.count("fruits.csv").read()
# or
row_count = qsv.count("fruits.csv", read=True)
```
### Get row count including header row and print to stdout
```python
qsv.count("fruits.csv").include_header_row().run()
# or
qsv.count("fruits.csv", include_header_row=True, run=True)
```
Output:
```console
4
```
Args:
file_path (str): The file to run `qsv count` on.
run (bool, optional): Execute the command without returning its output. Defaults to False.
read (bool, optional): Execute the command and return its output. Defaults to False.
include_header_row (bool, optional): Include the header row (first row) in the row count. Defaults to False.
human_readable (bool, optional): Comma separate row count. Defaults to False.
width (bool, optional): Also return the estimated length of the longest record. Defaults to False.
"""

args = []
if include_header_row:
args.append("-n")
if human_readable:
args.append("-H")
if width:
args.append("--width")

count_cmd = cmd("qsv", "count", file_path, *args)

if run:
return count_cmd.run()
if read:
return count_cmd.read()
return count_cmd


class CountBuilder(Expression):
def __init__(self):
self.file_path: str | None = None
self.args = []

def file(self, file_path: str):
"""
The file to run `qsv count` on.
"""
self.file_path = file_path
return self

def include_header_row(self):
"""
Include the header row (first row) in the row count.
"""
self.args.append("-n")
return self

def human_readable(self):
"""
Comma separate row count.
"""
self.args.append("-H")
return self

def width(self):
"""
Also return the estimated length of the longest record.
"""
self.args.append("--width")
return self

def run(self):
"""
Execute the command without returning its output.
"""
if not self.file_path:
return None
return cmd("qsv", "count", self.file_path, *self.args).run()

def read(self):
"""
Execute the command and return its output.
"""
return cmd("qsv", "count", self.file_path, *self.args).read()
Loading

0 comments on commit 8e6b113

Please sign in to comment.