dqdetect 0.1.0__py3-none-any.whl

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.
dqdetect/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
dqdetect/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ from dqdetect.cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()
dqdetect/checks.py ADDED
@@ -0,0 +1,69 @@
1
+ import pandas as pd
2
+ from pandas.api.types import is_numeric_dtype
3
+
4
+
5
+ DATE_NAME_HINTS = (
6
+ "date",
7
+ "time",
8
+ "timestamp",
9
+ "created",
10
+ "updated",
11
+ "dob",
12
+ )
13
+
14
+
15
+ def is_date_like_column(column_name):
16
+ name = str(column_name).lower()
17
+ return any(hint in name for hint in DATE_NAME_HINTS)
18
+
19
+
20
+ def mixed_numeric_text_count(series):
21
+ if is_numeric_dtype(series):
22
+ return 0
23
+
24
+ values = series.dropna().astype(str).str.strip()
25
+ values = values[values.ne("")]
26
+
27
+ if values.empty:
28
+ return 0
29
+
30
+ parsed = pd.to_numeric(values, errors="coerce")
31
+ numeric_count = int(parsed.notna().sum())
32
+ text_count = int(parsed.isna().sum())
33
+
34
+ if numeric_count == 0 or text_count == 0:
35
+ return 0
36
+
37
+ return min(numeric_count, text_count)
38
+
39
+
40
+ def invalid_date_count(series, column_name):
41
+ if not is_date_like_column(column_name):
42
+ return 0
43
+
44
+ values = series.dropna()
45
+ if values.empty:
46
+ return 0
47
+
48
+ parsed = pd.to_datetime(values, errors="coerce")
49
+ return int(parsed.isna().sum())
50
+
51
+
52
+ def iqr_outlier_count(series):
53
+ if not is_numeric_dtype(series):
54
+ return 0
55
+
56
+ values = series.dropna()
57
+ if len(values) < 4:
58
+ return 0
59
+
60
+ q1 = values.quantile(0.25)
61
+ q3 = values.quantile(0.75)
62
+ iqr = q3 - q1
63
+
64
+ if pd.isna(iqr) or iqr == 0:
65
+ return 0
66
+
67
+ lower = q1 - 1.5 * iqr
68
+ upper = q3 + 1.5 * iqr
69
+ return int(((values < lower) | (values > upper)).sum())
dqdetect/cli.py ADDED
@@ -0,0 +1,106 @@
1
+ import argparse
2
+ from pathlib import Path
3
+
4
+ from dqdetect.io import load_data
5
+ from dqdetect.profiler import profile_dataframe
6
+ from dqdetect.report import write_reports
7
+
8
+
9
+ def _issue_line(count, singular_subject, plural_subject, issue):
10
+ subject = singular_subject if count == 1 else plural_subject
11
+ verb = "contains" if count == 1 else "contain"
12
+ return f"- {count} {subject} {verb} {issue}"
13
+
14
+
15
+ def build_parser():
16
+ parser = argparse.ArgumentParser(
17
+ prog="dqdetect",
18
+ description="Profile a CSV or Excel file for common data-quality issues.",
19
+ )
20
+ parser.add_argument("file", help="Path to a CSV or Excel file")
21
+ parser.add_argument(
22
+ "--sheet",
23
+ help="Excel sheet name or index. Ignored for CSV files.",
24
+ )
25
+ parser.add_argument(
26
+ "--output",
27
+ default="reports",
28
+ help="Directory for generated reports (default: reports)",
29
+ )
30
+ parser.add_argument(
31
+ "--format",
32
+ choices=["md", "html", "both"],
33
+ default="both",
34
+ help="Report format (default: both)",
35
+ )
36
+ return parser
37
+
38
+
39
+ def main(argv=None):
40
+ parser = build_parser()
41
+ args = parser.parse_args(argv)
42
+
43
+ source = Path(args.file)
44
+
45
+ try:
46
+ dataframe = load_data(source, sheet=args.sheet)
47
+ except (FileNotFoundError, ValueError) as exc:
48
+ parser.error(str(exc))
49
+
50
+ profile = profile_dataframe(dataframe, source_name=source.name)
51
+ summary = profile["summary"]
52
+ written = write_reports(
53
+ profile,
54
+ output_dir=args.output,
55
+ stem=source.stem,
56
+ report_format=args.format,
57
+ )
58
+
59
+ print("Data Quality Detective")
60
+ print(f"File: {source}")
61
+ print()
62
+ print(f"Rows: {summary['rows']:,}")
63
+ print(f"Columns: {summary['columns']:,}")
64
+ print(f"Duplicate rows: {summary['duplicate_rows']:,}")
65
+ print()
66
+ print("Issues found")
67
+ print(
68
+ _issue_line(
69
+ summary["columns_with_missing"],
70
+ "column",
71
+ "columns",
72
+ "missing values",
73
+ )
74
+ )
75
+ print(
76
+ _issue_line(
77
+ summary["mixed_numeric_text_columns"],
78
+ "column",
79
+ "columns",
80
+ "mixed numeric/text values",
81
+ )
82
+ )
83
+ print(
84
+ _issue_line(
85
+ summary["date_columns_with_invalid_values"],
86
+ "date-like column",
87
+ "date-like columns",
88
+ "invalid values",
89
+ )
90
+ )
91
+ print(
92
+ _issue_line(
93
+ summary["numeric_columns_with_outliers"],
94
+ "numeric column",
95
+ "numeric columns",
96
+ "potential IQR outliers",
97
+ )
98
+ )
99
+ print()
100
+ print("Reports written to:")
101
+ for path in written:
102
+ print(f" {path}")
103
+
104
+
105
+ if __name__ == "__main__":
106
+ main()
dqdetect/io.py ADDED
@@ -0,0 +1,35 @@
1
+ from pathlib import Path
2
+
3
+ import pandas as pd
4
+
5
+
6
+ SUPPORTED_SUFFIXES = {".csv", ".xlsx"}
7
+
8
+
9
+ def _normalize_sheet(sheet):
10
+ if sheet is None:
11
+ return 0
12
+
13
+ if isinstance(sheet, str) and sheet.isdigit():
14
+ return int(sheet)
15
+
16
+ return sheet
17
+
18
+
19
+ def load_data(path, sheet=None):
20
+ source = Path(path)
21
+
22
+ if not source.exists():
23
+ raise FileNotFoundError(f"File not found: {source}")
24
+
25
+ suffix = source.suffix.lower()
26
+ if suffix not in SUPPORTED_SUFFIXES:
27
+ supported = ", ".join(sorted(SUPPORTED_SUFFIXES))
28
+ raise ValueError(
29
+ f"Unsupported file type '{suffix}'. Supported types: {supported}"
30
+ )
31
+
32
+ if suffix == ".csv":
33
+ return pd.read_csv(source)
34
+
35
+ return pd.read_excel(source, sheet_name=_normalize_sheet(sheet))
dqdetect/profiler.py ADDED
@@ -0,0 +1,86 @@
1
+ from pathlib import Path
2
+
3
+ import pandas as pd
4
+
5
+ from dqdetect.checks import (
6
+ invalid_date_count,
7
+ iqr_outlier_count,
8
+ mixed_numeric_text_count,
9
+ )
10
+
11
+
12
+ def _sample_values(series, limit=3):
13
+ values = series.dropna().astype(str).drop_duplicates().head(limit)
14
+ return [value[:60] for value in values]
15
+
16
+
17
+ def profile_dataframe(df, source_name="dataset"):
18
+ row_count, column_count = df.shape
19
+ duplicate_rows = int(df.duplicated().sum())
20
+
21
+ columns = []
22
+
23
+ for column in df.columns:
24
+ series = df[column]
25
+ missing_count = int(series.isna().sum())
26
+ unique_count = int(series.nunique(dropna=True))
27
+ mixed_count = mixed_numeric_text_count(series)
28
+ invalid_dates = invalid_date_count(series, column)
29
+ outliers = iqr_outlier_count(series)
30
+ is_constant = unique_count <= 1
31
+
32
+ issues = []
33
+ if missing_count:
34
+ issues.append("missing values")
35
+ if is_constant:
36
+ issues.append("constant column")
37
+ if mixed_count:
38
+ issues.append("mixed numeric/text")
39
+ if invalid_dates:
40
+ issues.append("invalid date values")
41
+ if outliers:
42
+ issues.append("potential outliers")
43
+
44
+ columns.append(
45
+ {
46
+ "column": str(column),
47
+ "dtype": str(series.dtype),
48
+ "missing_count": missing_count,
49
+ "missing_pct": round(
50
+ (missing_count / row_count * 100) if row_count else 0.0,
51
+ 2,
52
+ ),
53
+ "unique_count": unique_count,
54
+ "constant": is_constant,
55
+ "mixed_numeric_text_count": mixed_count,
56
+ "invalid_date_count": invalid_dates,
57
+ "outlier_count": outliers,
58
+ "sample_values": _sample_values(series),
59
+ "issues": issues,
60
+ }
61
+ )
62
+
63
+ summary = {
64
+ "source": Path(source_name).name,
65
+ "rows": row_count,
66
+ "columns": column_count,
67
+ "duplicate_rows": duplicate_rows,
68
+ "columns_with_missing": sum(
69
+ item["missing_count"] > 0 for item in columns
70
+ ),
71
+ "constant_columns": sum(item["constant"] for item in columns),
72
+ "mixed_numeric_text_columns": sum(
73
+ item["mixed_numeric_text_count"] > 0 for item in columns
74
+ ),
75
+ "date_columns_with_invalid_values": sum(
76
+ item["invalid_date_count"] > 0 for item in columns
77
+ ),
78
+ "numeric_columns_with_outliers": sum(
79
+ item["outlier_count"] > 0 for item in columns
80
+ ),
81
+ }
82
+
83
+ return {
84
+ "summary": summary,
85
+ "columns": columns,
86
+ }
dqdetect/report.py ADDED
@@ -0,0 +1,187 @@
1
+ from datetime import datetime, timezone
2
+ from html import escape
3
+ from pathlib import Path
4
+
5
+
6
+ def _recommendations(profile):
7
+ summary = profile["summary"]
8
+ recommendations = []
9
+
10
+ if summary["duplicate_rows"]:
11
+ recommendations.append(
12
+ "Review duplicate rows before aggregating or counting records."
13
+ )
14
+ if summary["columns_with_missing"]:
15
+ recommendations.append(
16
+ "Review missing values by column before deciding whether to impute, exclude, or preserve them."
17
+ )
18
+ if summary["mixed_numeric_text_columns"]:
19
+ recommendations.append(
20
+ "Keep mixed numeric/text source values separate from any cleaned numeric field instead of coercing text to zero."
21
+ )
22
+ if summary["date_columns_with_invalid_values"]:
23
+ recommendations.append(
24
+ "Inspect invalid date values before calculating durations or time-based KPIs."
25
+ )
26
+ if summary["numeric_columns_with_outliers"]:
27
+ recommendations.append(
28
+ "Investigate outliers before removing them; unusual values may be valid business events."
29
+ )
30
+ if summary["constant_columns"]:
31
+ recommendations.append(
32
+ "Constant columns may not add analytical value and can often be excluded from modeling."
33
+ )
34
+
35
+ if not recommendations:
36
+ recommendations.append(
37
+ "No issues were flagged by the current rules. Domain-specific validation may still be needed."
38
+ )
39
+
40
+ return recommendations
41
+
42
+
43
+ def render_markdown(profile):
44
+ summary = profile["summary"]
45
+ generated = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
46
+
47
+ lines = [
48
+ "# Data Quality Report",
49
+ "",
50
+ f"**Source:** {summary['source']}",
51
+ f"**Generated:** {generated}",
52
+ "",
53
+ "## Dataset summary",
54
+ "",
55
+ "| Metric | Value |",
56
+ "|---|---:|",
57
+ f"| Rows | {summary['rows']:,} |",
58
+ f"| Columns | {summary['columns']:,} |",
59
+ f"| Duplicate rows | {summary['duplicate_rows']:,} |",
60
+ f"| Columns with missing values | {summary['columns_with_missing']} |",
61
+ f"| Constant columns | {summary['constant_columns']} |",
62
+ f"| Mixed numeric/text columns | {summary['mixed_numeric_text_columns']} |",
63
+ f"| Date columns with invalid values | {summary['date_columns_with_invalid_values']} |",
64
+ f"| Numeric columns with potential outliers | {summary['numeric_columns_with_outliers']} |",
65
+ "",
66
+ "## Column profile",
67
+ "",
68
+ "| Column | Type | Missing | Unique | Mixed | Invalid dates | Outliers | Issues |",
69
+ "|---|---|---:|---:|---:|---:|---:|---|",
70
+ ]
71
+
72
+ for item in profile["columns"]:
73
+ issues = ", ".join(item["issues"]) if item["issues"] else "none"
74
+ lines.append(
75
+ "| {column} | {dtype} | {missing_count} ({missing_pct:.2f}%) | "
76
+ "{unique_count} | {mixed_numeric_text_count} | {invalid_date_count} | "
77
+ "{outlier_count} | {issues_text} |".format(**item, issues_text=issues)
78
+ )
79
+
80
+ lines.extend(["", "## Recommendations", ""])
81
+ for recommendation in _recommendations(profile):
82
+ lines.append(f"- {recommendation}")
83
+
84
+ lines.extend(
85
+ [
86
+ "",
87
+ "## Notes",
88
+ "",
89
+ "The checks in this report are screening rules, not automatic cleaning instructions. Business context should decide what gets changed.",
90
+ "",
91
+ ]
92
+ )
93
+
94
+ return "\n".join(lines)
95
+
96
+
97
+ def render_html(profile):
98
+ summary = profile["summary"]
99
+ generated = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
100
+
101
+ rows = []
102
+ for item in profile["columns"]:
103
+ issues = ", ".join(item["issues"]) if item["issues"] else "none"
104
+ rows.append(
105
+ "<tr>"
106
+ f"<td>{escape(item['column'])}</td>"
107
+ f"<td>{escape(item['dtype'])}</td>"
108
+ f"<td>{item['missing_count']} ({item['missing_pct']:.2f}%)</td>"
109
+ f"<td>{item['unique_count']}</td>"
110
+ f"<td>{item['mixed_numeric_text_count']}</td>"
111
+ f"<td>{item['invalid_date_count']}</td>"
112
+ f"<td>{item['outlier_count']}</td>"
113
+ f"<td>{escape(issues)}</td>"
114
+ "</tr>"
115
+ )
116
+
117
+ recommendation_items = "".join(
118
+ f"<li>{escape(item)}</li>" for item in _recommendations(profile)
119
+ )
120
+
121
+ return f"""<!doctype html>
122
+ <html lang="en">
123
+ <head>
124
+ <meta charset="utf-8">
125
+ <meta name="viewport" content="width=device-width, initial-scale=1">
126
+ <title>Data Quality Report</title>
127
+ <style>
128
+ body {{ font-family: Arial, sans-serif; max-width: 1100px; margin: 40px auto; padding: 0 24px; color: #202124; }}
129
+ h1, h2 {{ color: #172554; }}
130
+ .cards {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin: 24px 0; }}
131
+ .card {{ border: 1px solid #d9dee8; border-radius: 10px; padding: 16px; }}
132
+ .value {{ font-size: 1.5rem; font-weight: 700; margin-top: 6px; }}
133
+ table {{ border-collapse: collapse; width: 100%; font-size: 0.9rem; }}
134
+ th, td {{ border-bottom: 1px solid #e5e7eb; padding: 10px; text-align: left; vertical-align: top; }}
135
+ th {{ background: #f8fafc; }}
136
+ .note {{ background: #f8fafc; border-left: 4px solid #64748b; padding: 12px 16px; margin-top: 24px; }}
137
+ </style>
138
+ </head>
139
+ <body>
140
+ <h1>Data Quality Report</h1>
141
+ <p><strong>Source:</strong> {escape(summary['source'])}<br>
142
+ <strong>Generated:</strong> {generated}</p>
143
+
144
+ <div class="cards">
145
+ <div class="card">Rows<div class="value">{summary['rows']:,}</div></div>
146
+ <div class="card">Columns<div class="value">{summary['columns']}</div></div>
147
+ <div class="card">Duplicate rows<div class="value">{summary['duplicate_rows']}</div></div>
148
+ <div class="card">Columns with missing<div class="value">{summary['columns_with_missing']}</div></div>
149
+ </div>
150
+
151
+ <h2>Column profile</h2>
152
+ <table>
153
+ <thead>
154
+ <tr><th>Column</th><th>Type</th><th>Missing</th><th>Unique</th><th>Mixed</th><th>Invalid dates</th><th>Outliers</th><th>Issues</th></tr>
155
+ </thead>
156
+ <tbody>
157
+ {''.join(rows)}
158
+ </tbody>
159
+ </table>
160
+
161
+ <h2>Recommendations</h2>
162
+ <ul>{recommendation_items}</ul>
163
+
164
+ <div class="note">
165
+ These checks are screening rules, not automatic cleaning instructions. Business context should decide what gets changed.
166
+ </div>
167
+ </body>
168
+ </html>
169
+ """
170
+
171
+
172
+ def write_reports(profile, output_dir, stem, report_format="both"):
173
+ output = Path(output_dir)
174
+ output.mkdir(parents=True, exist_ok=True)
175
+ written = []
176
+
177
+ if report_format in {"md", "both"}:
178
+ markdown_path = output / f"{stem}_report.md"
179
+ markdown_path.write_text(render_markdown(profile), encoding="utf-8")
180
+ written.append(markdown_path)
181
+
182
+ if report_format in {"html", "both"}:
183
+ html_path = output / f"{stem}_report.html"
184
+ html_path.write_text(render_html(profile), encoding="utf-8")
185
+ written.append(html_path)
186
+
187
+ return written
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: dqdetect
3
+ Version: 0.1.0
4
+ Summary: Command-line data quality profiler for CSV and Excel files
5
+ Author: Shahed
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Shahedr/data-quality-detective
8
+ Project-URL: Repository, https://github.com/Shahedr/data-quality-detective
9
+ Project-URL: Issues, https://github.com/Shahedr/data-quality-detective/issues
10
+ Keywords: data-quality,data-profiling,csv,excel,analytics,pandas
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: pandas>=2.2
25
+ Requires-Dist: openpyxl>=3.1
26
+ Provides-Extra: dev
27
+ Requires-Dist: build>=1.2; extra == "dev"
28
+ Requires-Dist: pytest>=8.0; extra == "dev"
29
+ Requires-Dist: twine>=6.0; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # Data Quality Detective
33
+
34
+ [![tests](https://github.com/Shahedr/data-quality-detective/actions/workflows/tests.yml/badge.svg)](https://github.com/Shahedr/data-quality-detective/actions/workflows/tests.yml) ![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue) ![MIT License](https://img.shields.io/badge/license-MIT-green)
35
+
36
+ A small command-line tool for quickly profiling CSV and Excel files before analysis.
37
+
38
+ I built this around a problem I keep running into in analytics work: a dataset can look usable at first, but the real issues only show up after checking missing values, duplicate rows, mixed numeric/text fields, date parsing, and unusual numeric values.
39
+
40
+ **dqdetect** puts those checks into one repeatable command and writes a report you can review or share.
41
+
42
+ ## Quick start
43
+
44
+ ~~~bash
45
+ git clone https://github.com/Shahedr/data-quality-detective.git
46
+ cd data-quality-detective
47
+
48
+ python -m venv .venv
49
+ # macOS/Linux
50
+ source .venv/bin/activate
51
+ # Windows
52
+ # .venv/Scripts/activate
53
+
54
+ pip install -e .
55
+ dqdetect examples/messy_orders.csv
56
+ ~~~
57
+
58
+ By default the command writes both Markdown and HTML reports to the **reports/** folder.
59
+
60
+ ## What it checks
61
+
62
+ - dataset shape and column types
63
+ - duplicate rows
64
+ - missing values by column
65
+ - constant columns
66
+ - mixed numeric/text values
67
+ - invalid values in date-like columns
68
+ - IQR-based outlier counts for numeric columns
69
+
70
+ The tool does **not** automatically delete or "fix" anything. The report is meant to help an analyst decide what deserves review.
71
+
72
+ ## Example
73
+
74
+ ~~~text
75
+ Data Quality Detective
76
+ File: examples/messy_orders.csv
77
+
78
+ Rows: 12
79
+ Columns: 8
80
+ Duplicate rows: 1
81
+
82
+ Reports written to:
83
+ reports/messy_orders_report.md
84
+ reports/messy_orders_report.html
85
+ ~~~
86
+
87
+ See [examples/messy_orders_report.md](examples/messy_orders_report.md) for a sample report.
88
+
89
+ ## CLI
90
+
91
+ ~~~bash
92
+ dqdetect path/to/file.csv
93
+ dqdetect workbook.xlsx --sheet Orders\ndqdetect workbook.xlsx --sheet 0
94
+ dqdetect data.csv --format html
95
+ dqdetect data.csv --output audit_reports
96
+ ~~~
97
+
98
+ Supported formats:
99
+
100
+ - CSV
101
+ - Excel (.xlsx)
102
+
103
+ ## Why these checks?
104
+
105
+ This first version focuses on issues that can change an analysis if they are handled carelessly.
106
+
107
+ For example, a freight-cost field containing both dollar values and text such as "included elsewhere" should not simply be converted to zero. Likewise, an extreme numeric value may be a data-entry problem or a legitimate observation. The tool flags these cases instead of making the decision for you.
108
+
109
+ ## Project structure
110
+
111
+ ~~~text
112
+ data-quality-detective/
113
+ ├── dqdetect/
114
+ │ ├── __init__.py
115
+ │ ├── checks.py
116
+ │ ├── cli.py
117
+ │ ├── io.py
118
+ │ ├── profiler.py
119
+ │ └── report.py
120
+ ├── examples/
121
+ │ ├── messy_orders.csv
122
+ │ └── messy_orders_report.md
123
+ ├── tests/
124
+ ├── .github/workflows/tests.yml
125
+ ├── CONTRIBUTING.md
126
+ ├── LICENSE
127
+ └── pyproject.toml
128
+ ~~~
129
+
130
+ ## Development
131
+
132
+ ~~~bash
133
+ pip install -e ".[dev]"
134
+ pytest
135
+ ~~~
136
+
137
+ ## Roadmap
138
+
139
+ Things I would like to add next:
140
+
141
+ - user-configurable thresholds
142
+ - JSON output for automation
143
+ - schema rules for expected columns and types
144
+ - PostgreSQL table profiling
145
+ - comparison between two versions of a dataset
146
+ - richer HTML charts
147
+ - optional GitHub Action for automated data-quality checks
148
+
149
+ I am keeping the first release intentionally small so the checks are understandable and easy to extend.
150
+
151
+ ## License
152
+
153
+ MIT
@@ -0,0 +1,13 @@
1
+ dqdetect/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ dqdetect/__main__.py,sha256=sSpaYFy6kdpeNlcrd4cKpUPtg9H_obwQI2dBMcn0aoQ,70
3
+ dqdetect/checks.py,sha256=TJiRvliE4SxxxKIeYFf375mW4KEskGX4UEKLyKJfszU,1443
4
+ dqdetect/cli.py,sha256=7JouCNS37dYGvXnf2J6FyBCc6q0U5weTv8rAawJB-SI,2744
5
+ dqdetect/io.py,sha256=ZcFPmL29Edf0U1wrrJTEn_RWjr6CHhIEs7Gb4Cewhk0,782
6
+ dqdetect/profiler.py,sha256=yQVjzGAWEyNXia8WFgNxXppmK0RWrMxdD4xGWPUmWSM,2669
7
+ dqdetect/report.py,sha256=RQD6eHCOfyyrZZuVk48hCdlutqC_cGemZRyjPaKlCpQ,6845
8
+ dqdetect-0.1.0.dist-info/licenses/LICENSE,sha256=wji_CJxt9mbB6XDfopRCAmFqY1X-p_AKNlriT9YNy_I,1063
9
+ dqdetect-0.1.0.dist-info/METADATA,sha256=8ZEIBRjjdfDEKgs3lojhSYPGCo16qI4SD2jDbD8cGPk,4651
10
+ dqdetect-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
11
+ dqdetect-0.1.0.dist-info/entry_points.txt,sha256=ZF_jCESrxtDUUw6z7mKyVicpaANoCcCV4EF19EtyCQc,47
12
+ dqdetect-0.1.0.dist-info/top_level.txt,sha256=YekEoq6Y2Ho9r7uDXPXNrhJYkvmLYGmMHx5ssSg6oo0,9
13
+ dqdetect-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dqdetect = dqdetect.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shahed
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.
@@ -0,0 +1 @@
1
+ dqdetect