tinycontracts 0.1.0__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinycontracts
3
+ Version: 0.1.2
4
+ Summary: serve a folder of json/csv/parquet files as a rest api
5
+ Author-email: Aditya Kumar <adityakuma0308@gmail.com>
6
+ License-Expression: MIT
7
+ Keywords: api,cli,csv,fastapi,json,parquet,rest
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Framework :: FastAPI
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
16
+ Requires-Python: >=3.11
17
+ Requires-Dist: fastapi>=0.128.0
18
+ Requires-Dist: pandas>=3.0.0
19
+ Requires-Dist: pyarrow>=23.0.0
20
+ Requires-Dist: rich>=14.3.2
21
+ Requires-Dist: typer>=0.21.1
22
+ Requires-Dist: uvicorn>=0.40.0
23
+ Description-Content-Type: text/markdown
24
+
25
+ # tinycontracts
26
+
27
+ Turn any folder of JSON, CSV, or Parquet files into a REST API instantly.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install tinycontracts
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ tc ./data
39
+ ```
40
+
41
+ That's it. Your files are now API endpoints.
42
+
43
+ ## Options
44
+
45
+ ```bash
46
+ tc ./data -p 8000 # custom port (default: 4242)
47
+ tc ./data -H 0.0.0.0 # expose to network
48
+ tc --version # show version
49
+ ```
50
+
51
+ ## Example
52
+
53
+ ```
54
+ data/
55
+ users.json -> GET /users
56
+ orders.csv -> GET /orders
57
+ config.json -> GET /config
58
+ ```
59
+
60
+ ## Querying
61
+
62
+ ```bash
63
+ # filter
64
+ curl "localhost:4242/users?active=true"
65
+
66
+ # pagination
67
+ curl "localhost:4242/orders?_limit=10&_offset=20"
68
+
69
+ # sort
70
+ curl "localhost:4242/orders?_sort=-created_at"
71
+
72
+ # combine
73
+ curl "localhost:4242/orders?status=pending&_limit=5"
74
+ ```
75
+
76
+ ## Endpoints
77
+
78
+ | Endpoint | Description |
79
+ |----------|-------------|
80
+ | `/{resource}` | List all rows |
81
+ | `/{resource}/{id}` | Get by ID |
82
+ | `/{resource}/_schema` | JSON schema |
83
+ | `/_help` | API documentation |
84
+ | `/_schema` | All schemas |
85
+ | `/docs` | Swagger UI |
@@ -0,0 +1,61 @@
1
+ # tinycontracts
2
+
3
+ Turn any folder of JSON, CSV, or Parquet files into a REST API instantly.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install tinycontracts
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ tc ./data
15
+ ```
16
+
17
+ That's it. Your files are now API endpoints.
18
+
19
+ ## Options
20
+
21
+ ```bash
22
+ tc ./data -p 8000 # custom port (default: 4242)
23
+ tc ./data -H 0.0.0.0 # expose to network
24
+ tc --version # show version
25
+ ```
26
+
27
+ ## Example
28
+
29
+ ```
30
+ data/
31
+ users.json -> GET /users
32
+ orders.csv -> GET /orders
33
+ config.json -> GET /config
34
+ ```
35
+
36
+ ## Querying
37
+
38
+ ```bash
39
+ # filter
40
+ curl "localhost:4242/users?active=true"
41
+
42
+ # pagination
43
+ curl "localhost:4242/orders?_limit=10&_offset=20"
44
+
45
+ # sort
46
+ curl "localhost:4242/orders?_sort=-created_at"
47
+
48
+ # combine
49
+ curl "localhost:4242/orders?status=pending&_limit=5"
50
+ ```
51
+
52
+ ## Endpoints
53
+
54
+ | Endpoint | Description |
55
+ |----------|-------------|
56
+ | `/{resource}` | List all rows |
57
+ | `/{resource}/{id}` | Get by ID |
58
+ | `/{resource}/_schema` | JSON schema |
59
+ | `/_help` | API documentation |
60
+ | `/_schema` | All schemas |
61
+ | `/docs` | Swagger UI |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tinycontracts"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "serve a folder of json/csv/parquet files as a rest api"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -29,7 +29,7 @@ dependencies = [
29
29
  ]
30
30
 
31
31
  [project.scripts]
32
- tc = "tinycontracts.cli:app"
32
+ tc = "tinycontracts.cli:main"
33
33
 
34
34
  [build-system]
35
35
  requires = ["hatchling"]
@@ -0,0 +1,153 @@
1
+ # cli.py - fast startup with lazy imports
2
+ import sys
3
+
4
+
5
+ def main():
6
+ # fast path for --help and --version (no heavy imports)
7
+ args = sys.argv[1:]
8
+
9
+ if not args or args[0] in ("--help", "-h"):
10
+ print("""tc - serve data files as a rest api
11
+
12
+ usage: tc <folder> [options]
13
+
14
+ arguments:
15
+ folder folder containing json/csv/parquet files
16
+
17
+ options:
18
+ -p, --port PORT port to serve on (default: 4242)
19
+ -H, --host HOST host to bind to (default: 127.0.0.1)
20
+ -v, --version show version
21
+ -h, --help show this help""")
22
+ return
23
+
24
+ if args[0] in ("--version", "-v"):
25
+ print("tinycontracts v0.1.2")
26
+ return
27
+
28
+ # parse args manually for fast path
29
+ path = None
30
+ port = 4242
31
+ host = "127.0.0.1"
32
+
33
+ i = 0
34
+ while i < len(args):
35
+ arg = args[i]
36
+ if arg in ("-p", "--port"):
37
+ port = int(args[i + 1])
38
+ i += 2
39
+ elif arg in ("-H", "--host"):
40
+ host = args[i + 1]
41
+ i += 2
42
+ elif arg.startswith("-"):
43
+ print(f"error: unknown option {arg}")
44
+ sys.exit(1)
45
+ else:
46
+ path = arg
47
+ i += 1
48
+
49
+ if not path:
50
+ print("error: missing folder path\nusage: tc <folder>")
51
+ sys.exit(1)
52
+
53
+ # now import heavy deps
54
+ from pathlib import Path
55
+ from rich.console import Console
56
+
57
+ console = Console()
58
+ folder = Path(path)
59
+
60
+ if not folder.exists():
61
+ console.print(f"\n [red]error:[/red] path not found: [bold]{folder}[/bold]")
62
+ console.print(" [dim]make sure the path exists and try again[/dim]\n")
63
+ sys.exit(1)
64
+
65
+ if not folder.is_dir():
66
+ console.print(f"\n [red]error:[/red] not a folder: [bold]{folder}[/bold]")
67
+ console.print(" [dim]tc needs a folder path, not a file[/dim]\n")
68
+ sys.exit(1)
69
+
70
+ data_files = (
71
+ list(folder.glob("*.json"))
72
+ + list(folder.glob("*.csv"))
73
+ + list(folder.glob("*.parquet"))
74
+ )
75
+ if not data_files:
76
+ console.print(
77
+ f"\n [yellow]warning:[/yellow] no data files in [bold]{folder}[/bold]"
78
+ )
79
+ console.print(" [dim]add .json, .csv, or .parquet files[/dim]\n")
80
+
81
+ # lazy import heavy deps with spinner
82
+ with console.status("[cyan]loading...[/cyan]", spinner="dots"):
83
+ from tinycontracts.server import create_app
84
+ import uvicorn
85
+
86
+ try:
87
+ fastapi_app = create_app(folder)
88
+ resources = (
89
+ list(fastapi_app.state.resources.keys())
90
+ if hasattr(fastapi_app.state, "resources")
91
+ else []
92
+ )
93
+ except PermissionError:
94
+ console.print(f"\n [red]error:[/red] permission denied: [bold]{folder}[/bold]")
95
+ console.print(" [dim]check file permissions and try again[/dim]\n")
96
+ sys.exit(1)
97
+ except Exception as e:
98
+ console.print(f"\n [red]error:[/red] failed to load data: {e}\n")
99
+ sys.exit(1)
100
+
101
+ # print banner
102
+ from rich.panel import Panel
103
+ from rich.table import Table
104
+
105
+ console.print()
106
+ console.print(
107
+ Panel.fit(
108
+ "[bold cyan]tinycontracts[/bold cyan] [dim]v0.1.2[/dim]",
109
+ border_style="cyan",
110
+ )
111
+ )
112
+ console.print()
113
+
114
+ console.print(f" [dim]folder:[/dim] {folder.resolve()}")
115
+ console.print(f" [dim]server:[/dim] [green]http://{host}:{port}[/green]")
116
+ console.print()
117
+
118
+ if resources:
119
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
120
+ table.add_column("endpoint", style="green")
121
+ table.add_column("description", style="dim")
122
+
123
+ for name in sorted(resources):
124
+ table.add_row(f"/{name}", f"query {name}")
125
+
126
+ table.add_row("", "")
127
+ table.add_row("/docs", "swagger ui")
128
+ table.add_row("/_help", "api help")
129
+
130
+ console.print(table)
131
+ else:
132
+ console.print(" [yellow]no data files found[/yellow]")
133
+
134
+ console.print()
135
+ console.print(" [dim]press ctrl+c to stop[/dim]")
136
+ console.print()
137
+
138
+ # run server
139
+ try:
140
+ uvicorn.run(fastapi_app, host=host, port=port, log_level="warning")
141
+ except KeyboardInterrupt:
142
+ console.print("\n [dim]stopped[/dim]\n")
143
+ except OSError as e:
144
+ if "address already in use" in str(e).lower() or "10048" in str(e):
145
+ console.print(f"\n [red]error:[/red] port {port} already in use")
146
+ console.print(f" [dim]try: tc {path} -p {port + 1}[/dim]\n")
147
+ else:
148
+ console.print(f"\n [red]error:[/red] {e}\n")
149
+ sys.exit(1)
150
+
151
+
152
+ if __name__ == "__main__":
153
+ main()
@@ -484,7 +484,7 @@ wheels = [
484
484
 
485
485
  [[package]]
486
486
  name = "tinycontracts"
487
- version = "0.1.0"
487
+ version = "0.1.2"
488
488
  source = { editable = "." }
489
489
  dependencies = [
490
490
  { name = "fastapi" },
@@ -1,73 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: tinycontracts
3
- Version: 0.1.0
4
- Summary: serve a folder of json/csv/parquet files as a rest api
5
- Author-email: Aditya Kumar <adityakuma0308@gmail.com>
6
- License-Expression: MIT
7
- Keywords: api,cli,csv,fastapi,json,parquet,rest
8
- Classifier: Development Status :: 4 - Beta
9
- Classifier: Environment :: Console
10
- Classifier: Framework :: FastAPI
11
- Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
16
- Requires-Python: >=3.11
17
- Requires-Dist: fastapi>=0.128.0
18
- Requires-Dist: pandas>=3.0.0
19
- Requires-Dist: pyarrow>=23.0.0
20
- Requires-Dist: rich>=14.3.2
21
- Requires-Dist: typer>=0.21.1
22
- Requires-Dist: uvicorn>=0.40.0
23
- Description-Content-Type: text/markdown
24
-
25
- # tinycontracts
26
-
27
- Turn JSON, CSV, and Parquet files into REST APIs instantly.
28
-
29
- ## Install
30
-
31
- ```bash
32
- uv sync
33
- ```
34
-
35
- ## Usage
36
-
37
- ```bash
38
- tinycontracts serve ./data
39
- ```
40
-
41
- This scans `./data` for `.json`, `.csv`, and `.parquet` files and serves them as REST endpoints.
42
-
43
- ## Example
44
-
45
- Given this structure:
46
- ```
47
- data/
48
- users.json
49
- orders.csv
50
- products.parquet
51
- ```
52
-
53
- You get:
54
- ```
55
- GET /users
56
- GET /orders
57
- GET /products
58
- ```
59
-
60
- ## Filtering
61
-
62
- Query any field:
63
- ```
64
- GET /users?role=admin
65
- GET /orders?status=shipped&customer_id=123
66
- ```
67
-
68
- ## Schema
69
-
70
- Get the inferred JSON schema:
71
- ```
72
- GET /users/schema
73
- ```
@@ -1,49 +0,0 @@
1
- # tinycontracts
2
-
3
- Turn JSON, CSV, and Parquet files into REST APIs instantly.
4
-
5
- ## Install
6
-
7
- ```bash
8
- uv sync
9
- ```
10
-
11
- ## Usage
12
-
13
- ```bash
14
- tinycontracts serve ./data
15
- ```
16
-
17
- This scans `./data` for `.json`, `.csv`, and `.parquet` files and serves them as REST endpoints.
18
-
19
- ## Example
20
-
21
- Given this structure:
22
- ```
23
- data/
24
- users.json
25
- orders.csv
26
- products.parquet
27
- ```
28
-
29
- You get:
30
- ```
31
- GET /users
32
- GET /orders
33
- GET /products
34
- ```
35
-
36
- ## Filtering
37
-
38
- Query any field:
39
- ```
40
- GET /users?role=admin
41
- GET /orders?status=shipped&customer_id=123
42
- ```
43
-
44
- ## Schema
45
-
46
- Get the inferred JSON schema:
47
- ```
48
- GET /users/schema
49
- ```
@@ -1,134 +0,0 @@
1
- # cli.py - typer entry point
2
- import sys
3
- from pathlib import Path
4
-
5
- import typer
6
- import uvicorn
7
- from rich.console import Console
8
- from rich.panel import Panel
9
- from rich.table import Table
10
-
11
- from tinycontracts.server import create_app
12
-
13
- console = Console()
14
-
15
- app = typer.Typer(
16
- name="tc",
17
- help="serve a folder of json/csv/parquet files as a rest api",
18
- no_args_is_help=True,
19
- add_completion=False,
20
- )
21
-
22
-
23
- def print_banner(host: str, port: int, resources: list[str], folder: Path):
24
- # header
25
- console.print()
26
- console.print(
27
- Panel.fit(
28
- "[bold cyan]tinycontracts[/bold cyan] [dim]v0.1.0[/dim]",
29
- border_style="cyan",
30
- )
31
- )
32
- console.print()
33
-
34
- # server info
35
- console.print(f" [dim]folder:[/dim] {folder.resolve()}")
36
- console.print(f" [dim]server:[/dim] [green]http://{host}:{port}[/green]")
37
- console.print()
38
-
39
- # endpoints table
40
- if resources:
41
- table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
42
- table.add_column("endpoint", style="green")
43
- table.add_column("description", style="dim")
44
-
45
- for name in sorted(resources):
46
- table.add_row(f"/{name}", f"query {name}")
47
-
48
- table.add_row("", "")
49
- table.add_row("/docs", "swagger ui")
50
- table.add_row("/_help", "api help")
51
-
52
- console.print(table)
53
- else:
54
- console.print(" [yellow]no data files found[/yellow]")
55
-
56
- console.print()
57
- console.print(" [dim]press ctrl+c to stop[/dim]")
58
- console.print()
59
-
60
-
61
- @app.command()
62
- def serve(
63
- path: str = typer.Argument(..., help="folder containing data files"),
64
- port: int = typer.Option(4242, "--port", "-p", help="port to serve on"),
65
- host: str = typer.Option("127.0.0.1", "--host", "-H", help="host to bind to"),
66
- ):
67
- """serve data files as a rest api"""
68
- folder = Path(path)
69
-
70
- # validate folder exists
71
- if not folder.exists():
72
- console.print(f"\n [red]error:[/red] path not found: [bold]{folder}[/bold]")
73
- console.print(f" [dim]make sure the path exists and try again[/dim]\n")
74
- raise typer.Exit(1)
75
-
76
- # validate it's a directory
77
- if not folder.is_dir():
78
- console.print(f"\n [red]error:[/red] not a folder: [bold]{folder}[/bold]")
79
- console.print(f" [dim]tc needs a folder path, not a file[/dim]\n")
80
- raise typer.Exit(1)
81
-
82
- # check for data files
83
- data_files = (
84
- list(folder.glob("*.json"))
85
- + list(folder.glob("*.csv"))
86
- + list(folder.glob("*.parquet"))
87
- )
88
- if not data_files:
89
- console.print(
90
- f"\n [yellow]warning:[/yellow] no data files in [bold]{folder}[/bold]"
91
- )
92
- console.print("[dim]add .json, .csv, or .parquet files[/dim]\n")
93
-
94
- # create app
95
- try:
96
- fastapi_app = create_app(folder)
97
- resources = (
98
- list(fastapi_app.state.resources.keys())
99
- if hasattr(fastapi_app.state, "resources")
100
- else []
101
- )
102
- except PermissionError:
103
- console.print(f"\n [red]error:[/red] permission denied: [bold]{folder}[/bold]")
104
- console.print("[dim]check file permissions and try again[/dim]\n")
105
- raise typer.Exit(1)
106
- except Exception as e:
107
- console.print(f"\n [red]error:[/red] failed to load data: {e}\n")
108
- raise typer.Exit(1)
109
-
110
- # print startup banner
111
- print_banner(host, port, resources, folder)
112
-
113
- # run server
114
- try:
115
- uvicorn.run(fastapi_app, host=host, port=port, log_level="warning")
116
- except KeyboardInterrupt:
117
- console.print("\n [dim]stopped[/dim]\n")
118
- except OSError as e:
119
- if "address already in use" in str(e).lower() or "10048" in str(e):
120
- console.print(f"\n [red]error:[/red] port {port} already in use")
121
- console.print(f" [dim]try: tc {path} -p {port + 1}[/dim]\n")
122
- else:
123
- console.print(f"\n [red]error:[/red] {e}\n")
124
- raise typer.Exit(1)
125
-
126
-
127
- @app.command()
128
- def version():
129
- """show version"""
130
- console.print("tinycontracts [cyan]v0.1.0[/cyan]")
131
-
132
-
133
- if __name__ == "__main__":
134
- app()
File without changes