datanyx 0.1.0__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.
- datanyx-0.1.0/LICENSE +21 -0
- datanyx-0.1.0/PKG-INFO +146 -0
- datanyx-0.1.0/README.md +84 -0
- datanyx-0.1.0/pyproject.toml +130 -0
- datanyx-0.1.0/setup.cfg +4 -0
- datanyx-0.1.0/src/datanyx/__init__.py +117 -0
- datanyx-0.1.0/src/datanyx/__main__.py +5 -0
- datanyx-0.1.0/src/datanyx/audit.py +194 -0
- datanyx-0.1.0/src/datanyx/check/__init__.py +125 -0
- datanyx-0.1.0/src/datanyx/check/model.py +179 -0
- datanyx-0.1.0/src/datanyx/check/python_check.py +368 -0
- datanyx-0.1.0/src/datanyx/check/sql_check.py +328 -0
- datanyx-0.1.0/src/datanyx/cli.py +1220 -0
- datanyx-0.1.0/src/datanyx/config.py +349 -0
- datanyx-0.1.0/src/datanyx/diff.py +239 -0
- datanyx-0.1.0/src/datanyx/evaluation.py +223 -0
- datanyx-0.1.0/src/datanyx/fileperms.py +29 -0
- datanyx-0.1.0/src/datanyx/formats.py +52 -0
- datanyx-0.1.0/src/datanyx/inference.py +275 -0
- datanyx-0.1.0/src/datanyx/keys.py +231 -0
- datanyx-0.1.0/src/datanyx/mcp_server.py +365 -0
- datanyx-0.1.0/src/datanyx/privacy.py +77 -0
- datanyx-0.1.0/src/datanyx/protect/__init__.py +21 -0
- datanyx-0.1.0/src/datanyx/protect/config.py +91 -0
- datanyx-0.1.0/src/datanyx/protect/guard.py +345 -0
- datanyx-0.1.0/src/datanyx/protect/settings.py +266 -0
- datanyx-0.1.0/src/datanyx/pseudonymize/__init__.py +23 -0
- datanyx-0.1.0/src/datanyx/pseudonymize/fakes.py +246 -0
- datanyx-0.1.0/src/datanyx/pseudonymize/keys.py +41 -0
- datanyx-0.1.0/src/datanyx/pseudonymize/plan.py +204 -0
- datanyx-0.1.0/src/datanyx/pseudonymize/tables.py +285 -0
- datanyx-0.1.0/src/datanyx/renderers/__init__.py +22 -0
- datanyx-0.1.0/src/datanyx/renderers/json_schema.py +114 -0
- datanyx-0.1.0/src/datanyx/renderers/naming.py +39 -0
- datanyx-0.1.0/src/datanyx/renderers/pydantic.py +139 -0
- datanyx-0.1.0/src/datanyx/renderers/sql_ddl.py +108 -0
- datanyx-0.1.0/src/datanyx/renderers/text.py +80 -0
- datanyx-0.1.0/src/datanyx/renderers/typescript.py +90 -0
- datanyx-0.1.0/src/datanyx/scale.py +27 -0
- datanyx-0.1.0/src/datanyx/scan.py +304 -0
- datanyx-0.1.0/src/datanyx/schema.py +118 -0
- datanyx-0.1.0/src/datanyx/scrub.py +200 -0
- datanyx-0.1.0/src/datanyx/sources/__init__.py +73 -0
- datanyx-0.1.0/src/datanyx/sources/avro_source.py +141 -0
- datanyx-0.1.0/src/datanyx/sources/base.py +36 -0
- datanyx-0.1.0/src/datanyx/sources/catalog.py +43 -0
- datanyx-0.1.0/src/datanyx/sources/delimited_source.py +73 -0
- datanyx-0.1.0/src/datanyx/sources/directory.py +140 -0
- datanyx-0.1.0/src/datanyx/sources/duckdb_source.py +199 -0
- datanyx-0.1.0/src/datanyx/sources/excel_source.py +83 -0
- datanyx-0.1.0/src/datanyx/sources/fhir_source.py +90 -0
- datanyx-0.1.0/src/datanyx/sources/files.py +119 -0
- datanyx-0.1.0/src/datanyx/sources/hl7_source.py +109 -0
- datanyx-0.1.0/src/datanyx/sources/json_source.py +158 -0
- datanyx-0.1.0/src/datanyx/sources/mongodb_source.py +136 -0
- datanyx-0.1.0/src/datanyx/sources/orc_source.py +36 -0
- datanyx-0.1.0/src/datanyx/sources/parquet_source.py +113 -0
- datanyx-0.1.0/src/datanyx/sources/sql_source.py +392 -0
- datanyx-0.1.0/src/datanyx/sources/stats_source.py +132 -0
- datanyx-0.1.0/src/datanyx/sources/xml_source.py +101 -0
- datanyx-0.1.0/src/datanyx/sources/yaml_source.py +53 -0
- datanyx-0.1.0/src/datanyx/synth/__init__.py +8 -0
- datanyx-0.1.0/src/datanyx/synth/generator.py +328 -0
- datanyx-0.1.0/src/datanyx/synth/writers.py +110 -0
- datanyx-0.1.0/src/datanyx/text/__init__.py +29 -0
- datanyx-0.1.0/src/datanyx/text/anonymizer.py +285 -0
- datanyx-0.1.0/src/datanyx/text/backends.py +259 -0
- datanyx-0.1.0/src/datanyx/text/batch.py +164 -0
- datanyx-0.1.0/src/datanyx/text/custom.py +166 -0
- datanyx-0.1.0/src/datanyx/text/detectors.py +263 -0
- datanyx-0.1.0/src/datanyx/text/documents.py +464 -0
- datanyx-0.1.0/src/datanyx/text/mapping.py +79 -0
- datanyx-0.1.0/src/datanyx/text/packs.py +189 -0
- datanyx-0.1.0/src/datanyx/text/secrets.py +55 -0
- datanyx-0.1.0/src/datanyx.egg-info/PKG-INFO +146 -0
- datanyx-0.1.0/src/datanyx.egg-info/SOURCES.txt +109 -0
- datanyx-0.1.0/src/datanyx.egg-info/dependency_links.txt +1 -0
- datanyx-0.1.0/src/datanyx.egg-info/entry_points.txt +2 -0
- datanyx-0.1.0/src/datanyx.egg-info/requires.txt +64 -0
- datanyx-0.1.0/src/datanyx.egg-info/top_level.txt +1 -0
- datanyx-0.1.0/tests/test_audit.py +203 -0
- datanyx-0.1.0/tests/test_check.py +392 -0
- datanyx-0.1.0/tests/test_cli_process.py +89 -0
- datanyx-0.1.0/tests/test_config.py +209 -0
- datanyx-0.1.0/tests/test_custom_detectors.py +168 -0
- datanyx-0.1.0/tests/test_detection_quality.py +483 -0
- datanyx-0.1.0/tests/test_diff.py +236 -0
- datanyx-0.1.0/tests/test_docs.py +85 -0
- datanyx-0.1.0/tests/test_documents.py +589 -0
- datanyx-0.1.0/tests/test_file_sources.py +323 -0
- datanyx-0.1.0/tests/test_fuzz_leaks.py +129 -0
- datanyx-0.1.0/tests/test_keep_name.py +122 -0
- datanyx-0.1.0/tests/test_mcp_server.py +149 -0
- datanyx-0.1.0/tests/test_mongodb_server.py +59 -0
- datanyx-0.1.0/tests/test_more_sources.py +477 -0
- datanyx-0.1.0/tests/test_privacy.py +199 -0
- datanyx-0.1.0/tests/test_protect.py +553 -0
- datanyx-0.1.0/tests/test_pseudonymize.py +453 -0
- datanyx-0.1.0/tests/test_renderers.py +101 -0
- datanyx-0.1.0/tests/test_rich_schemas.py +324 -0
- datanyx-0.1.0/tests/test_scale_servers.py +51 -0
- datanyx-0.1.0/tests/test_scan.py +226 -0
- datanyx-0.1.0/tests/test_schema.py +197 -0
- datanyx-0.1.0/tests/test_scrub.py +227 -0
- datanyx-0.1.0/tests/test_shipping.py +76 -0
- datanyx-0.1.0/tests/test_sql_servers.py +396 -0
- datanyx-0.1.0/tests/test_sql_targets.py +88 -0
- datanyx-0.1.0/tests/test_sqlite.py +374 -0
- datanyx-0.1.0/tests/test_synth.py +251 -0
- datanyx-0.1.0/tests/test_text.py +422 -0
- datanyx-0.1.0/tests/test_text_model.py +65 -0
datanyx-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ludenberg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
datanyx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datanyx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Turn sensitive data into a value-free schema that AI agents can work from.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: ai-agents,privacy,schema,anonymization,data
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Security
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: sqlalchemy>=2
|
|
21
|
+
Requires-Dist: tomli>=2; python_version < "3.11"
|
|
22
|
+
Provides-Extra: postgres
|
|
23
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == "postgres"
|
|
24
|
+
Provides-Extra: mysql
|
|
25
|
+
Requires-Dist: pymysql>=1.1; extra == "mysql"
|
|
26
|
+
Requires-Dist: cryptography; extra == "mysql"
|
|
27
|
+
Provides-Extra: mssql
|
|
28
|
+
Requires-Dist: pymssql>=2.3; extra == "mssql"
|
|
29
|
+
Provides-Extra: oracle
|
|
30
|
+
Requires-Dist: oracledb>=2; extra == "oracle"
|
|
31
|
+
Provides-Extra: parquet
|
|
32
|
+
Requires-Dist: pyarrow>=14; extra == "parquet"
|
|
33
|
+
Provides-Extra: excel
|
|
34
|
+
Requires-Dist: openpyxl>=3.1; extra == "excel"
|
|
35
|
+
Provides-Extra: yaml
|
|
36
|
+
Requires-Dist: pyyaml>=6; extra == "yaml"
|
|
37
|
+
Provides-Extra: streaming
|
|
38
|
+
Requires-Dist: ijson>=3.2; extra == "streaming"
|
|
39
|
+
Provides-Extra: mcp
|
|
40
|
+
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
41
|
+
Provides-Extra: avro
|
|
42
|
+
Requires-Dist: fastavro>=1.9; extra == "avro"
|
|
43
|
+
Provides-Extra: orc
|
|
44
|
+
Requires-Dist: pyarrow>=14; extra == "orc"
|
|
45
|
+
Provides-Extra: duckdb
|
|
46
|
+
Requires-Dist: duckdb>=1.0; extra == "duckdb"
|
|
47
|
+
Provides-Extra: stats
|
|
48
|
+
Requires-Dist: pyreadstat>=1.2; (sys_platform != "win32" or python_version >= "3.11") and extra == "stats"
|
|
49
|
+
Provides-Extra: mongodb
|
|
50
|
+
Requires-Dist: pymongo>=4.6; extra == "mongodb"
|
|
51
|
+
Provides-Extra: all
|
|
52
|
+
Requires-Dist: datanyx[avro,check,duckdb,excel,mcp,mongodb,mssql,mysql,oracle,orc,parquet,pdf,postgres,presidio,stats,streaming,yaml]; extra == "all"
|
|
53
|
+
Provides-Extra: text
|
|
54
|
+
Requires-Dist: spacy>=3.7; extra == "text"
|
|
55
|
+
Provides-Extra: check
|
|
56
|
+
Requires-Dist: sqlglot>=25; extra == "check"
|
|
57
|
+
Provides-Extra: pdf
|
|
58
|
+
Requires-Dist: pypdf>=4; extra == "pdf"
|
|
59
|
+
Provides-Extra: presidio
|
|
60
|
+
Requires-Dist: presidio-analyzer>=2.2; extra == "presidio"
|
|
61
|
+
Dynamic: license-file
|
|
62
|
+
|
|
63
|
+
<div align="center">
|
|
64
|
+
|
|
65
|
+
# DataNyx
|
|
66
|
+
|
|
67
|
+
**Let an AI agent work with your data without ever seeing it.**
|
|
68
|
+
|
|
69
|
+
DataNyx turns files and databases into value-free schemas an agent can write code against,
|
|
70
|
+
and replaces names and identifiers in free text with placeholders.
|
|
71
|
+
|
|
72
|
+
[](LICENSE)
|
|
73
|
+
[](pyproject.toml)
|
|
74
|
+
[](https://docs.astral.sh/ruff/)
|
|
75
|
+
[](https://mypy-lang.org/)
|
|
76
|
+
|
|
77
|
+
[Getting started](docs/getting-started.md) ·
|
|
78
|
+
[Documentation](docs/index.md) ·
|
|
79
|
+
[Command reference](docs/cli-reference.md) ·
|
|
80
|
+
[Security model](docs/security.md) ·
|
|
81
|
+
[Changelog](CHANGELOG.md)
|
|
82
|
+
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
You want an agent to write a report, a query or a data-cleaning script, but the data is customers,
|
|
88
|
+
patients or payroll. Pasting rows into a chat is a leak, and describing the columns by hand is
|
|
89
|
+
tedious. DataNyx sits in between:
|
|
90
|
+
|
|
91
|
+
| | What the agent gets | What it never gets |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| **Structured data**<br>`datanyx schema` | Field names, types, formats, nesting, primary and foreign keys | Values, examples, enum labels, counts or statistics |
|
|
94
|
+
| **Free text**<br>`datanyx anonymize` | The text with `<PERSON_1>`, `<EMAIL_1>` and friends in place of identifiers | The identifiers themselves (best effort: [review the result](docs/anonymization.md#how-reliable-text-anonymization-is)) |
|
|
95
|
+
|
|
96
|
+
## A first look
|
|
97
|
+
|
|
98
|
+
```console
|
|
99
|
+
$ datanyx schema customers.csv --format text
|
|
100
|
+
root: array<{
|
|
101
|
+
id: integer
|
|
102
|
+
email: string<email>
|
|
103
|
+
signed_up: string<date>
|
|
104
|
+
plan: string
|
|
105
|
+
lifetime_value: number
|
|
106
|
+
notes: string | null
|
|
107
|
+
}>
|
|
108
|
+
|
|
109
|
+
$ datanyx anonymize call-notes.txt
|
|
110
|
+
<PERSON_1> (<EMAIL_1>) called on <DATE_1> about invoice
|
|
111
|
+
<ID_1>. Ms. <PERSON_1> lives in <LOCATION_1>. Her manager <PERSON_2> agreed.
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The agent sees the shape of the table and the story in the text, and none of the values. It can also
|
|
115
|
+
try its code on [fake rows](docs/synthetic-data.md) generated from the schema.
|
|
116
|
+
|
|
117
|
+
## Install
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
git clone <this repository> datanyx && cd datanyx
|
|
121
|
+
uv sync --all-extras # or: pip install -e ".[all]"
|
|
122
|
+
datanyx doctor # shows which optional packages and models are installed
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Python 3.10 or newer. [Supported sources](docs/sources.md) lists the extra for each format.
|
|
126
|
+
|
|
127
|
+
## What's in the documentation
|
|
128
|
+
|
|
129
|
+
| | |
|
|
130
|
+
|---|---|
|
|
131
|
+
| [Getting started](docs/getting-started.md) | Install, a first schema, a first anonymized text |
|
|
132
|
+
| [Schemas](docs/schemas.md), [synthetic data](docs/synthetic-data.md) | The core: what the agent sees instead of the data |
|
|
133
|
+
| [Debugging](docs/debugging.md), [checking code](docs/checking-code.md) | Fixing code without seeing the data |
|
|
134
|
+
| [Scanning](docs/scanning.md), [anonymization](docs/anonymization.md), [pseudonymization](docs/pseudonymization.md), [detection quality](docs/detection.md) | Finding and hiding personal data |
|
|
135
|
+
| [Using it with an agent](docs/agents.md) | `datanyx protect`, the guard hook and the MCP server |
|
|
136
|
+
| [Installing and deploying](docs/deployment.md) | PyPI, Docker, a pre-commit hook, a GitHub Action, a shared MCP server |
|
|
137
|
+
| [Team settings](docs/settings.md), [schema diff](docs/schema-diff.md) | Shared policy, and change detection for CI |
|
|
138
|
+
| [Security model](docs/security.md) | What is guaranteed, what isn't, and the known limits |
|
|
139
|
+
| [Command reference](docs/cli-reference.md), [Python API](docs/python-api.md) | Every option, and use from code |
|
|
140
|
+
| [Contributing](docs/contributing.md) | Architecture, adding a source, tests |
|
|
141
|
+
|
|
142
|
+
To read it as a site: `uv sync --group docs` then `uv run mkdocs serve`.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
[MIT](LICENSE) © 2026 ludenberg
|
datanyx-0.1.0/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# DataNyx
|
|
4
|
+
|
|
5
|
+
**Let an AI agent work with your data without ever seeing it.**
|
|
6
|
+
|
|
7
|
+
DataNyx turns files and databases into value-free schemas an agent can write code against,
|
|
8
|
+
and replaces names and identifiers in free text with placeholders.
|
|
9
|
+
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
[](pyproject.toml)
|
|
12
|
+
[](https://docs.astral.sh/ruff/)
|
|
13
|
+
[](https://mypy-lang.org/)
|
|
14
|
+
|
|
15
|
+
[Getting started](docs/getting-started.md) ·
|
|
16
|
+
[Documentation](docs/index.md) ·
|
|
17
|
+
[Command reference](docs/cli-reference.md) ·
|
|
18
|
+
[Security model](docs/security.md) ·
|
|
19
|
+
[Changelog](CHANGELOG.md)
|
|
20
|
+
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
You want an agent to write a report, a query or a data-cleaning script, but the data is customers,
|
|
26
|
+
patients or payroll. Pasting rows into a chat is a leak, and describing the columns by hand is
|
|
27
|
+
tedious. DataNyx sits in between:
|
|
28
|
+
|
|
29
|
+
| | What the agent gets | What it never gets |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| **Structured data**<br>`datanyx schema` | Field names, types, formats, nesting, primary and foreign keys | Values, examples, enum labels, counts or statistics |
|
|
32
|
+
| **Free text**<br>`datanyx anonymize` | The text with `<PERSON_1>`, `<EMAIL_1>` and friends in place of identifiers | The identifiers themselves (best effort: [review the result](docs/anonymization.md#how-reliable-text-anonymization-is)) |
|
|
33
|
+
|
|
34
|
+
## A first look
|
|
35
|
+
|
|
36
|
+
```console
|
|
37
|
+
$ datanyx schema customers.csv --format text
|
|
38
|
+
root: array<{
|
|
39
|
+
id: integer
|
|
40
|
+
email: string<email>
|
|
41
|
+
signed_up: string<date>
|
|
42
|
+
plan: string
|
|
43
|
+
lifetime_value: number
|
|
44
|
+
notes: string | null
|
|
45
|
+
}>
|
|
46
|
+
|
|
47
|
+
$ datanyx anonymize call-notes.txt
|
|
48
|
+
<PERSON_1> (<EMAIL_1>) called on <DATE_1> about invoice
|
|
49
|
+
<ID_1>. Ms. <PERSON_1> lives in <LOCATION_1>. Her manager <PERSON_2> agreed.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The agent sees the shape of the table and the story in the text, and none of the values. It can also
|
|
53
|
+
try its code on [fake rows](docs/synthetic-data.md) generated from the schema.
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
git clone <this repository> datanyx && cd datanyx
|
|
59
|
+
uv sync --all-extras # or: pip install -e ".[all]"
|
|
60
|
+
datanyx doctor # shows which optional packages and models are installed
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Python 3.10 or newer. [Supported sources](docs/sources.md) lists the extra for each format.
|
|
64
|
+
|
|
65
|
+
## What's in the documentation
|
|
66
|
+
|
|
67
|
+
| | |
|
|
68
|
+
|---|---|
|
|
69
|
+
| [Getting started](docs/getting-started.md) | Install, a first schema, a first anonymized text |
|
|
70
|
+
| [Schemas](docs/schemas.md), [synthetic data](docs/synthetic-data.md) | The core: what the agent sees instead of the data |
|
|
71
|
+
| [Debugging](docs/debugging.md), [checking code](docs/checking-code.md) | Fixing code without seeing the data |
|
|
72
|
+
| [Scanning](docs/scanning.md), [anonymization](docs/anonymization.md), [pseudonymization](docs/pseudonymization.md), [detection quality](docs/detection.md) | Finding and hiding personal data |
|
|
73
|
+
| [Using it with an agent](docs/agents.md) | `datanyx protect`, the guard hook and the MCP server |
|
|
74
|
+
| [Installing and deploying](docs/deployment.md) | PyPI, Docker, a pre-commit hook, a GitHub Action, a shared MCP server |
|
|
75
|
+
| [Team settings](docs/settings.md), [schema diff](docs/schema-diff.md) | Shared policy, and change detection for CI |
|
|
76
|
+
| [Security model](docs/security.md) | What is guaranteed, what isn't, and the known limits |
|
|
77
|
+
| [Command reference](docs/cli-reference.md), [Python API](docs/python-api.md) | Every option, and use from code |
|
|
78
|
+
| [Contributing](docs/contributing.md) | Architecture, adding a source, tests |
|
|
79
|
+
|
|
80
|
+
To read it as a site: `uv sync --group docs` then `uv run mkdocs serve`.
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
[MIT](LICENSE) © 2026 ludenberg
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "datanyx"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Turn sensitive data into a value-free schema that AI agents can work from."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
keywords = ["ai-agents", "privacy", "schema", "anonymization", "data"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: Security",
|
|
24
|
+
"Topic :: Software Development :: Libraries",
|
|
25
|
+
]
|
|
26
|
+
dependencies = ["sqlalchemy>=2", "tomli>=2; python_version < '3.11'"]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
postgres = ["psycopg[binary]>=3.1"]
|
|
30
|
+
mysql = ["pymysql>=1.1", "cryptography"]
|
|
31
|
+
mssql = ["pymssql>=2.3"]
|
|
32
|
+
oracle = ["oracledb>=2"]
|
|
33
|
+
parquet = ["pyarrow>=14"]
|
|
34
|
+
excel = ["openpyxl>=3.1"]
|
|
35
|
+
yaml = ["pyyaml>=6"]
|
|
36
|
+
streaming = ["ijson>=3.2"]
|
|
37
|
+
mcp = ["mcp>=1.2"]
|
|
38
|
+
avro = ["fastavro>=1.9"]
|
|
39
|
+
orc = ["pyarrow>=14"]
|
|
40
|
+
duckdb = ["duckdb>=1.0"]
|
|
41
|
+
# pyreadstat publishes no Windows wheel for Python 3.10, and building it there needs a C library, so
|
|
42
|
+
# the extra is empty on that one platform and SAS, Stata and SPSS files report that they need it.
|
|
43
|
+
stats = ["pyreadstat>=1.2; sys_platform != 'win32' or python_version >= '3.11'"]
|
|
44
|
+
mongodb = ["pymongo>=4.6"]
|
|
45
|
+
all = ["datanyx[postgres,mysql,mssql,oracle,parquet,excel,yaml,streaming,mcp,check,pdf,presidio,avro,orc,duckdb,stats,mongodb]"]
|
|
46
|
+
text = ["spacy>=3.7"]
|
|
47
|
+
check = [
|
|
48
|
+
"sqlglot>=25",
|
|
49
|
+
]
|
|
50
|
+
pdf = [
|
|
51
|
+
"pypdf>=4",
|
|
52
|
+
]
|
|
53
|
+
presidio = [
|
|
54
|
+
"presidio-analyzer>=2.2",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[dependency-groups]
|
|
58
|
+
dev = [
|
|
59
|
+
"pytest>=8",
|
|
60
|
+
"pytest-cov>=5",
|
|
61
|
+
"hypothesis>=6",
|
|
62
|
+
"ruff>=0.6",
|
|
63
|
+
"mypy>=1.10",
|
|
64
|
+
"types-PyYAML",
|
|
65
|
+
"pandas>=2.3.3",
|
|
66
|
+
"mongomock>=4.1",
|
|
67
|
+
]
|
|
68
|
+
docs = [
|
|
69
|
+
"mkdocs>=1.6",
|
|
70
|
+
"mkdocs-material>=9.5",
|
|
71
|
+
]
|
|
72
|
+
# Large NER models, installed from spaCy's releases. Needed only by the text tests.
|
|
73
|
+
models = [
|
|
74
|
+
"en-core-web-lg @ https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.8.0/en_core_web_lg-3.8.0-py3-none-any.whl",
|
|
75
|
+
"fr-core-news-md @ https://github.com/explosion/spacy-models/releases/download/fr_core_news_md-3.8.0/fr_core_news_md-3.8.0-py3-none-any.whl",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[project.scripts]
|
|
79
|
+
datanyx = "datanyx.cli:main"
|
|
80
|
+
|
|
81
|
+
[tool.setuptools.packages.find]
|
|
82
|
+
where = ["src"]
|
|
83
|
+
|
|
84
|
+
[tool.pytest.ini_options]
|
|
85
|
+
testpaths = ["tests"]
|
|
86
|
+
pythonpath = ["src"]
|
|
87
|
+
markers = [
|
|
88
|
+
"server: needs a real database server, started from tests/docker-compose.yml",
|
|
89
|
+
"needs_model: runs the default spaCy model, which only some CI jobs install",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
[tool.ruff]
|
|
93
|
+
line-length = 120
|
|
94
|
+
target-version = "py310"
|
|
95
|
+
src = ["src", "tests"]
|
|
96
|
+
|
|
97
|
+
[tool.ruff.lint]
|
|
98
|
+
select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF"]
|
|
99
|
+
ignore = [
|
|
100
|
+
"E501", "B904", "SIM105", "RUF012",
|
|
101
|
+
"B905", # zip() on rows of unequal length is intended: short rows leave columns out.
|
|
102
|
+
"RUF001", # Typographic apostrophes and dotless i are test data and word lists.
|
|
103
|
+
"SIM115", # Streams that must outlive a `with` block (stdin, SQLite handles).
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[tool.ruff.lint.per-file-ignores]
|
|
107
|
+
"tests/*" = ["B017", "B028", "SIM117"]
|
|
108
|
+
|
|
109
|
+
[tool.mypy]
|
|
110
|
+
python_version = "3.10"
|
|
111
|
+
files = ["src/datanyx"]
|
|
112
|
+
ignore_missing_imports = true
|
|
113
|
+
warn_unused_ignores = true
|
|
114
|
+
check_untyped_defs = true
|
|
115
|
+
|
|
116
|
+
[tool.coverage.run]
|
|
117
|
+
source = ["src/datanyx"]
|
|
118
|
+
branch = true
|
|
119
|
+
|
|
120
|
+
[tool.coverage.report]
|
|
121
|
+
show_missing = true
|
|
122
|
+
skip_covered = true
|
|
123
|
+
|
|
124
|
+
[[tool.mypy.overrides]]
|
|
125
|
+
module = "datanyx.mcp_server"
|
|
126
|
+
# Which of the two MCP library versions is installed decides which ignores are needed.
|
|
127
|
+
warn_unused_ignores = false
|
|
128
|
+
|
|
129
|
+
[tool.uv]
|
|
130
|
+
default-groups = ["dev"]
|
datanyx-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""DataNyx: describe data to an AI agent without showing it the data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from dataclasses import replace
|
|
8
|
+
|
|
9
|
+
from .keys import Evidence, apply_evidence
|
|
10
|
+
from .privacy import KeyPolicy
|
|
11
|
+
from .renderers import RENDERERS
|
|
12
|
+
from .schema import DataSchema
|
|
13
|
+
from .sources import Source, SourceError, describe_folder, resolve_source
|
|
14
|
+
from .text import DEFAULT_MODEL
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def extract(
|
|
20
|
+
target: str,
|
|
21
|
+
source: str | None = None,
|
|
22
|
+
policy: KeyPolicy | None = None,
|
|
23
|
+
namespace: str | None = None,
|
|
24
|
+
recursive: bool = False,
|
|
25
|
+
) -> DataSchema:
|
|
26
|
+
"""Build a value-free schema for a file path, "-" for stdin, a database URL or a folder.
|
|
27
|
+
|
|
28
|
+
`namespace` picks a database schema; the database's default is used otherwise.
|
|
29
|
+
For a folder, every readable file in it is described (and in its subfolders,
|
|
30
|
+
when `recursive` is set); `source` then limits which files are read.
|
|
31
|
+
"""
|
|
32
|
+
policy = policy or KeyPolicy()
|
|
33
|
+
if policy.infer_keys and policy.evidence is None:
|
|
34
|
+
policy = replace(policy, evidence=Evidence())
|
|
35
|
+
if os.path.isdir(target):
|
|
36
|
+
if namespace is not None:
|
|
37
|
+
raise SourceError("a schema can only be selected for SQL databases")
|
|
38
|
+
schema = describe_folder(target, source, policy, recursive)
|
|
39
|
+
else:
|
|
40
|
+
schema = resolve_source(target, source).extract(target, policy, namespace)
|
|
41
|
+
if policy.infer_keys and policy.evidence is not None:
|
|
42
|
+
schema = apply_evidence(schema, policy.evidence)
|
|
43
|
+
if not any(e.foreign_keys or _has_unique(e) for e in schema.entities):
|
|
44
|
+
schema = replace(schema, notes=(*schema.notes, "no unique columns or joins were inferred; this kind of source may not support it, or no key-looking column qualified"))
|
|
45
|
+
if policy.scale_hints and not any(entity.scale for entity in schema.entities):
|
|
46
|
+
schema = replace(schema, notes=(*schema.notes, "no row-count hints: this kind of source can't give one without reading it"))
|
|
47
|
+
return schema
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _has_unique(entity) -> bool:
|
|
51
|
+
node = getattr(entity.node, "items", entity.node)
|
|
52
|
+
return any(field.unique for field in getattr(node, "fields", {}).values())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def render(schema: DataSchema, output_format: str = "json-schema") -> str:
|
|
56
|
+
"""Render a schema in one of the formats listed in `RENDERERS`."""
|
|
57
|
+
return RENDERERS[output_format](schema)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def schema_of(
|
|
61
|
+
target: str,
|
|
62
|
+
output_format: str = "json-schema",
|
|
63
|
+
*,
|
|
64
|
+
source: str | None = None,
|
|
65
|
+
map_paths: Iterable[str] = (),
|
|
66
|
+
keep_names: Iterable[str] = (),
|
|
67
|
+
namespace: str | None = None,
|
|
68
|
+
recursive: bool = False,
|
|
69
|
+
) -> str:
|
|
70
|
+
"""The schema of `target` as text, in one call.
|
|
71
|
+
|
|
72
|
+
>>> print(datanyx.schema_of("customers.csv", "text"))
|
|
73
|
+
|
|
74
|
+
Raises `SourceError` with a message that never quotes the data. Use `extract` and
|
|
75
|
+
`render` instead to get the schema as objects, or to see its `notes`.
|
|
76
|
+
"""
|
|
77
|
+
policy = KeyPolicy(map_paths=frozenset(map_paths), keep_names=frozenset(keep_names))
|
|
78
|
+
schema = extract(target, source, policy, namespace, recursive)
|
|
79
|
+
return render(schema, output_format)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def anonymize_text(
|
|
83
|
+
text: str,
|
|
84
|
+
*,
|
|
85
|
+
model: str = DEFAULT_MODEL,
|
|
86
|
+
hide: Iterable[str] = (),
|
|
87
|
+
keep: Iterable[str] = (),
|
|
88
|
+
keep_entity: Iterable[str] = (),
|
|
89
|
+
) -> str:
|
|
90
|
+
"""`text` with names and identifiers replaced by placeholders such as <PERSON_1>.
|
|
91
|
+
|
|
92
|
+
Needs `datanyx[text]` and a spaCy model. Best effort: review the result.
|
|
93
|
+
"""
|
|
94
|
+
from .text import Detector, PatternDetector, SpacyDetector, anonymize
|
|
95
|
+
|
|
96
|
+
detectors: list[Detector] = [PatternDetector(), SpacyDetector(model)]
|
|
97
|
+
return anonymize(text, detectors, hide, keep, keep_entity)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def synthesize(schema: DataSchema, rows: int = 100, seed: int | None = 0):
|
|
101
|
+
"""Fake data matching `schema`, as a list of `datanyx.synth.Table`. See `datanyx.synth`."""
|
|
102
|
+
from .synth import synthesize as generate
|
|
103
|
+
|
|
104
|
+
return generate(schema, rows, seed)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
__all__ = [
|
|
108
|
+
"DataSchema",
|
|
109
|
+
"KeyPolicy",
|
|
110
|
+
"Source",
|
|
111
|
+
"SourceError",
|
|
112
|
+
"anonymize_text",
|
|
113
|
+
"extract",
|
|
114
|
+
"render",
|
|
115
|
+
"schema_of",
|
|
116
|
+
"synthesize",
|
|
117
|
+
]
|