tabalyst 0.6.0__tar.gz → 0.6.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.
Files changed (205) hide show
  1. {tabalyst-0.6.0 → tabalyst-0.6.2}/MANIFEST.in +1 -0
  2. {tabalyst-0.6.0/src/tabalyst.egg-info → tabalyst-0.6.2}/PKG-INFO +69 -7
  3. {tabalyst-0.6.0 → tabalyst-0.6.2}/README.md +67 -6
  4. tabalyst-0.6.2/examples/README.md +144 -0
  5. {tabalyst-0.6.0 → tabalyst-0.6.2}/pyproject.toml +3 -2
  6. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/__init__.py +8 -0
  7. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/batch.py +11 -3
  8. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/cli/app.py +2 -0
  9. tabalyst-0.6.2/src/tabalyst/cli/generate.py +67 -0
  10. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/cli/inspect.py +6 -9
  11. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/cli/report.py +26 -5
  12. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/cli/scan.py +8 -2
  13. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/cli/terminal.py +18 -14
  14. tabalyst-0.6.2/src/tabalyst/generate_anomalies.py +126 -0
  15. tabalyst-0.6.2/src/tabalyst/generate_csv.py +34 -0
  16. tabalyst-0.6.2/src/tabalyst/generate_definition.py +596 -0
  17. tabalyst-0.6.2/src/tabalyst/generate_examples/crm.yaml +70 -0
  18. tabalyst-0.6.2/src/tabalyst/generate_examples/insurance.yaml +71 -0
  19. tabalyst-0.6.2/src/tabalyst/generate_insurance.py +208 -0
  20. tabalyst-0.6.2/src/tabalyst/generate_references.py +221 -0
  21. tabalyst-0.6.2/src/tabalyst/generate_resources/SOURCES.md +27 -0
  22. tabalyst-0.6.2/src/tabalyst/generate_resources/addresses/street_directions_ca.csv +9 -0
  23. tabalyst-0.6.2/src/tabalyst/generate_resources/addresses/street_names_ca.csv +109 -0
  24. tabalyst-0.6.2/src/tabalyst/generate_resources/addresses/street_types_ca.csv +32 -0
  25. tabalyst-0.6.2/src/tabalyst/generate_resources/addresses/unit_types_ca.csv +12 -0
  26. tabalyst-0.6.2/src/tabalyst/generate_resources/contact/email_patterns.csv +12 -0
  27. tabalyst-0.6.2/src/tabalyst/generate_resources/contact/email_providers_ca.csv +10 -0
  28. tabalyst-0.6.2/src/tabalyst/generate_resources/contact/phone_area_codes_ca.csv +14 -0
  29. tabalyst-0.6.2/src/tabalyst/generate_resources/geography/cities_ca.csv +72 -0
  30. tabalyst-0.6.2/src/tabalyst/generate_resources/geography/postal_prefixes_ca.csv +72 -0
  31. tabalyst-0.6.2/src/tabalyst/generate_resources/geography/provinces_ca.csv +14 -0
  32. tabalyst-0.6.2/src/tabalyst/generate_resources/insurance/SOURCES.md +23 -0
  33. tabalyst-0.6.2/src/tabalyst/generate_resources/insurance/agencies.csv +21 -0
  34. tabalyst-0.6.2/src/tabalyst/generate_resources/insurance/branch_contract_weights.csv +13 -0
  35. tabalyst-0.6.2/src/tabalyst/generate_resources/insurance/claim_statuses.csv +5 -0
  36. tabalyst-0.6.2/src/tabalyst/generate_resources/insurance/contract_statuses.csv +5 -0
  37. tabalyst-0.6.2/src/tabalyst/generate_resources/insurance/contract_types.csv +5 -0
  38. tabalyst-0.6.2/src/tabalyst/generate_resources/insurance/payment_frequencies.csv +5 -0
  39. tabalyst-0.6.2/src/tabalyst/generate_resources/insurance/payment_methods.csv +5 -0
  40. tabalyst-0.6.2/src/tabalyst/generate_resources/people/family_names_ca.csv +136 -0
  41. tabalyst-0.6.2/src/tabalyst/generate_resources/people/first_names_en_ca.csv +81 -0
  42. tabalyst-0.6.2/src/tabalyst/generate_resources/people/first_names_fr_qc.csv +101 -0
  43. tabalyst-0.6.2/src/tabalyst/generate_service.py +367 -0
  44. tabalyst-0.6.2/src/tabalyst/generate_values.py +199 -0
  45. tabalyst-0.6.2/src/tabalyst/inspector/choices.py +114 -0
  46. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/models.py +2 -2
  47. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/resolution.py +39 -4
  48. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/models.py +2 -2
  49. tabalyst-0.6.2/src/tabalyst/option_notices.py +54 -0
  50. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/report_service.py +185 -4
  51. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scan_service.py +17 -1
  52. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/models.py +3 -3
  53. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/static/theme.css +1 -0
  54. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/templates/column.html +2 -2
  55. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/templates/report.html +2 -2
  56. {tabalyst-0.6.0 → tabalyst-0.6.2/src/tabalyst.egg-info}/PKG-INFO +69 -7
  57. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst.egg-info/SOURCES.txt +41 -0
  58. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst.egg-info/requires.txt +1 -0
  59. tabalyst-0.6.2/tests/test_all_collections.py +312 -0
  60. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_cli.py +2 -2
  61. tabalyst-0.6.2/tests/test_generate.py +368 -0
  62. tabalyst-0.6.2/tests/test_generate_crm.py +51 -0
  63. tabalyst-0.6.2/tests/test_generate_insurance.py +73 -0
  64. tabalyst-0.6.2/tests/test_generate_lot4.py +122 -0
  65. tabalyst-0.6.2/tests/test_generate_references.py +116 -0
  66. tabalyst-0.6.2/tests/test_option_notices.py +89 -0
  67. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_index.py +5 -3
  68. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_reporting.py +4 -3
  69. tabalyst-0.6.0/examples/README.md +0 -75
  70. {tabalyst-0.6.0 → tabalyst-0.6.2}/LICENSE +0 -0
  71. {tabalyst-0.6.0 → tabalyst-0.6.2}/examples/config.json +0 -0
  72. {tabalyst-0.6.0 → tabalyst-0.6.2}/examples/input/basic.csv +0 -0
  73. {tabalyst-0.6.0 → tabalyst-0.6.2}/examples/input/insurance-customers.csv +0 -0
  74. {tabalyst-0.6.0 → tabalyst-0.6.2}/setup.cfg +0 -0
  75. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/__main__.py +0 -0
  76. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/_version.py +0 -0
  77. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/cache_service.py +0 -0
  78. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/cli/__init__.py +0 -0
  79. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/cli/cache.py +0 -0
  80. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/cli/sample.py +0 -0
  81. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/config.py +0 -0
  82. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/errors.py +0 -0
  83. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/execution_log.py +0 -0
  84. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspect_service.py +0 -0
  85. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/__init__.py +0 -0
  86. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/excel_inspect/__init__.py +0 -0
  87. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/excel_inspect/build.py +0 -0
  88. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/excel_inspect/detect.py +0 -0
  89. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/excel_inspect/parameters.py +0 -0
  90. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/json_inspect/__init__.py +0 -0
  91. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/json_inspect/build.py +0 -0
  92. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/json_inspect/detect.py +0 -0
  93. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/json_inspect/parameters.py +0 -0
  94. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/inspector/persistence.py +0 -0
  95. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/progress.py +0 -0
  96. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/project_open_service.py +0 -0
  97. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/project_scan_service.py +0 -0
  98. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/__init__.py +0 -0
  99. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_bounded_values.py +0 -0
  100. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_cache.py +0 -0
  101. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_codec.py +0 -0
  102. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_generation.py +0 -0
  103. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_locks.py +0 -0
  104. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_publication.py +0 -0
  105. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_queries.py +0 -0
  106. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_query_budget.py +0 -0
  107. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_recovery.py +0 -0
  108. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_retention.py +0 -0
  109. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_schema.py +0 -0
  110. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_session.py +0 -0
  111. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_staging.py +0 -0
  112. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_sync.py +0 -0
  113. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/_validation.py +0 -0
  114. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/catalog.py +0 -0
  115. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/freshness.py +0 -0
  116. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/identity.py +0 -0
  117. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/index.py +0 -0
  118. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/location.py +0 -0
  119. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/models.py +0 -0
  120. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/projects/store.py +0 -0
  121. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/report_config.py +0 -0
  122. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/report_profile.py +0 -0
  123. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/reporting.py +0 -0
  124. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/sampling.py +0 -0
  125. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/sampling_service.py +0 -0
  126. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scan_reuse.py +0 -0
  127. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/__init__.py +0 -0
  128. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/api.py +0 -0
  129. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/config.py +0 -0
  130. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/__init__.py +0 -0
  131. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/amount.py +0 -0
  132. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/base.py +0 -0
  133. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/boolean.py +0 -0
  134. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/currency.py +0 -0
  135. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/email.py +0 -0
  136. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/enumeration.py +0 -0
  137. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/ip.py +0 -0
  138. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/names.py +0 -0
  139. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/number.py +0 -0
  140. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/pattern.py +0 -0
  141. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/percentage.py +0 -0
  142. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/phone.py +0 -0
  143. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/postal.py +0 -0
  144. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/quantity.py +0 -0
  145. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/registry.py +0 -0
  146. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/shape.py +0 -0
  147. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/temporal.py +0 -0
  148. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/url.py +0 -0
  149. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/detectors/uuid.py +0 -0
  150. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/diagnostics.py +0 -0
  151. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/engine.py +0 -0
  152. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/exposure.py +0 -0
  153. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/field.py +0 -0
  154. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/identity.py +0 -0
  155. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/measures.py +0 -0
  156. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/normalization.py +0 -0
  157. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/observations.py +0 -0
  158. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/paths.py +0 -0
  159. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/readers/__init__.py +0 -0
  160. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/readers/base.py +0 -0
  161. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/readers/csv_reader.py +0 -0
  162. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/readers/excel_common.py +0 -0
  163. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/readers/excel_reader.py +0 -0
  164. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/readers/excel_table.py +0 -0
  165. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/readers/json_common.py +0 -0
  166. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/readers/json_reader.py +0 -0
  167. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/readers/jsonl_reader.py +0 -0
  168. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/records.py +0 -0
  169. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/structure.py +0 -0
  170. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/technical.py +0 -0
  171. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/values.py +0 -0
  172. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/scanner/workers.py +0 -0
  173. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/service.py +0 -0
  174. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/shared_scan_service.py +0 -0
  175. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst/static/report.js +0 -0
  176. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst.egg-info/dependency_links.txt +0 -0
  177. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst.egg-info/entry_points.txt +0 -0
  178. {tabalyst-0.6.0 → tabalyst-0.6.2}/src/tabalyst.egg-info/top_level.txt +0 -0
  179. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_license_headers.py +0 -0
  180. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_cache.py +0 -0
  181. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_database.py +0 -0
  182. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_generations.py +0 -0
  183. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_identity.py +0 -0
  184. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_location.py +0 -0
  185. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_open_service.py +0 -0
  186. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_queries.py +0 -0
  187. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_rescan.py +0 -0
  188. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_scan.py +0 -0
  189. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_sessions.py +0 -0
  190. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_project_store.py +0 -0
  191. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_public_api.py +0 -0
  192. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_sampling.py +0 -0
  193. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_scan_command.py +0 -0
  194. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_scanner_amounts.py +0 -0
  195. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_scanner_batches.py +0 -0
  196. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_scanner_config.py +0 -0
  197. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_scanner_csv.py +0 -0
  198. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_scanner_email_url.py +0 -0
  199. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_scanner_exposure.py +0 -0
  200. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_scanner_paths.py +0 -0
  201. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_scanner_phone.py +0 -0
  202. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_scanner_postal.py +0 -0
  203. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_scanner_uuid_ip.py +0 -0
  204. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_shared_scan_cli.py +0 -0
  205. {tabalyst-0.6.0 → tabalyst-0.6.2}/tests/test_source_freshness.py +0 -0
@@ -3,3 +3,4 @@ include README.md
3
3
  include examples/README.md
4
4
  include examples/config.json
5
5
  recursive-include examples/input *.csv
6
+ recursive-include src/tabalyst/generate_resources *.csv *.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tabalyst
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Open-source toolkit for understanding and working with unfamiliar data.
5
5
  Author: Gregory Borelli
6
6
  License-Expression: MPL-2.0
@@ -27,6 +27,7 @@ Requires-Dist: jinja2>=3.1
27
27
  Requires-Dist: platformdirs>=4
28
28
  Requires-Dist: pydantic>=2.11
29
29
  Requires-Dist: python-calamine>=0.8
30
+ Requires-Dist: PyYAML>=6.0
30
31
  Requires-Dist: typer>=0.12
31
32
  Provides-Extra: dev
32
33
  Requires-Dist: build>=1.2; extra == "dev"
@@ -48,7 +49,8 @@ Its first tool is **Tabalyst Report**. The current CSV implementation,
48
49
  profile and a self-contained interactive HTML report.
49
50
  **Tabalyst Scan** describes CSV, JSON, JSONL and Excel files in a scan document,
50
51
  **Tabalyst Inspect** finds how to read a JSON, JSONL or Excel file, and
51
- **Tabalyst Sample** creates smaller CSV files.
52
+ **Tabalyst Sample** creates smaller CSV files by sampling, and **Tabalyst
53
+ Generate** creates synthetic CSV datasets.
52
54
 
53
55
  Tabalyst is in beta. Its interfaces may still change while
54
56
  the shared toolkit architecture is being established.
@@ -75,6 +77,7 @@ changes often: update it before each new test.
75
77
  | One JSON file | `tabalyst report data.json` | `data.report.html` beside the source |
76
78
  | One JSONL file | `tabalyst report events.jsonl` | `events.report.html` beside the source |
77
79
  | One Excel workbook | `tabalyst report sales.xlsx` | `sales.report.html` beside the source |
80
+ | Every table of a workbook | `tabalyst report shop.xlsx --all-collections` | `shop.costs.html`, `shop.sales.html`, ... beside the source |
78
81
  | From a scan document | `tabalyst report --scan data.scan.json` | `data.html` beside the scan |
79
82
 
80
83
  The simplest command keeps the source filename:
@@ -143,9 +146,17 @@ tabalyst report sales.xlsx
143
146
 
144
147
  Cells keep their Excel type, and dates are read as dates. A workbook with one
145
148
  table needs nothing else. When several sheets or tables are equally plausible,
146
- Tabalyst stops and lists them instead of choosing: pass `--collection Costs` (or
147
- `'$.Sales.Orders'` for a named table), or see [Tabalyst Inspect](#tabalyst-inspect).
148
- Older `.xls` files are not read: save them as `.xlsx`.
149
+ Tabalyst stops and prints one command per table to copy and run, such as
150
+ `tabalyst report shop.xlsx --collection Costs` (or `Sales.Orders` for a named
151
+ table), or see [Tabalyst Inspect](#tabalyst-inspect). To report every table at
152
+ once, use `--all-collections`:
153
+
154
+ ```console
155
+ tabalyst report shop.xlsx --all-collections -d reports
156
+ ```
157
+
158
+ This writes `shop.costs.html` and `shop.costs.json` for each table, named after
159
+ the file and the sheet. Older `.xls` files are not read: save them as `.xlsx`.
149
160
 
150
161
  ### Multiple files
151
162
 
@@ -264,6 +275,33 @@ Sampling reads CSV records as a stream. Random and stratified sampling keep
264
275
  only the requested sample, plus stratum counts, in memory. Existing outputs
265
276
  require `--force`, and an input file is never overwritten.
266
277
 
278
+ ## Tabalyst Generate
279
+
280
+ Generate synthetic CSV data from a versioned YAML definition. The packaged
281
+ `crm` example writes one contacts file; `insurance`
282
+ writes three branch files and a combined file:
283
+
284
+ ```console
285
+ tabalyst generate --example crm -o crm-contacts.csv
286
+ tabalyst generate --example insurance --rows 3000 -d insurance-output/
287
+ tabalyst generate --example insurance --rows 3000 --anomaly-profile dirty_realistic -d insurance-dirty/
288
+ ```
289
+
290
+ You can pass `examples/generate/input/crm.yaml` or
291
+ `examples/generate/input/insurance.yaml` instead of `--example`. A selected anomaly
292
+ profile writes an altered CSV, a cell change log and a summary beside the
293
+ untouched clean CSV. Insurance's profile changes only the combined file.
294
+ `--rows 3000` apportions 2,182, 273 and 545 rows to the three branches.
295
+ Existing outputs require `--force`.
296
+
297
+ Run `python examples/generate/generate_examples.py` to generate every YAML
298
+ from `examples/generate/input/` into ignored local output folders.
299
+
300
+ Insurance preserves the 33 source columns and the combined `filiale` column of
301
+ the existing public demo, but its generated rows are different. The branch date
302
+ and identifier formats are preserved; `filiale` is the last column in the new
303
+ combined CSV. The current public demo and report remain unchanged.
304
+
267
305
  ## Tabalyst Inspect
268
306
 
269
307
  A JSON file can hold several arrays, and an Excel workbook several sheets.
@@ -417,7 +455,9 @@ scan = tabalyst.scan("orders.json")
417
455
  scans = tabalyst.generate_scans(["data/*.json"], output_dir="scans")
418
456
 
419
457
  inspection = tabalyst.inspect("orders.json")
420
- inspections = tabalyst.generate_inspections(["data/*.json", "logs/*.jsonl", "*.xlsx"])
458
+ inspections = tabalyst.generate_inspections(
459
+ ["data/*.json", "logs/*.jsonl", "*.xlsx"]
460
+ )
421
461
  reports = tabalyst.generate_reports(["scans/*.scan.json"], from_scan=True)
422
462
  ```
423
463
 
@@ -503,8 +543,30 @@ Additional documentation:
503
543
  Please report defects and feature requests through the
504
544
  [GitHub issue tracker](https://github.com/loribel-labs/tabalyst/issues).
505
545
 
546
+ ## For AI assistants
547
+
548
+ To get help from an AI assistant, paste this prompt into a chat that can read
549
+ web pages:
550
+
551
+ ```text
552
+ Read the complete Tabalyst documentation at
553
+ https://docs.tabalyst.com/llms-full.txt, then help me use Tabalyst well.
554
+ Ask me what my data looks like and what I want to learn from it, then suggest
555
+ the right commands and options. Base your answers only on that documentation.
556
+ ```
557
+
558
+ If the assistant cannot open links, paste the content of the file instead.
559
+
560
+ The documentation site publishes plain-text versions of its pages:
561
+ [llms.txt](https://docs.tabalyst.com/llms.txt) lists every page with a
562
+ one-line description, and
563
+ [llms-full.txt](https://docs.tabalyst.com/llms-full.txt) holds the whole
564
+ documentation in one file. Every page is also available as Markdown by adding
565
+ `.md` to its URL, for example
566
+ [report.md](https://docs.tabalyst.com/report.md).
567
+
506
568
  ## License
507
569
 
508
570
  Tabalyst is released under the [Mozilla Public License 2.0](LICENSE).
509
571
 
510
- Created by Gregory Borelli — Catalyseur Numérique.
572
+ Created by Gregory Borelli. Open source project by Loribel Labs.
@@ -10,7 +10,8 @@ Its first tool is **Tabalyst Report**. The current CSV implementation,
10
10
  profile and a self-contained interactive HTML report.
11
11
  **Tabalyst Scan** describes CSV, JSON, JSONL and Excel files in a scan document,
12
12
  **Tabalyst Inspect** finds how to read a JSON, JSONL or Excel file, and
13
- **Tabalyst Sample** creates smaller CSV files.
13
+ **Tabalyst Sample** creates smaller CSV files by sampling, and **Tabalyst
14
+ Generate** creates synthetic CSV datasets.
14
15
 
15
16
  Tabalyst is in beta. Its interfaces may still change while
16
17
  the shared toolkit architecture is being established.
@@ -37,6 +38,7 @@ changes often: update it before each new test.
37
38
  | One JSON file | `tabalyst report data.json` | `data.report.html` beside the source |
38
39
  | One JSONL file | `tabalyst report events.jsonl` | `events.report.html` beside the source |
39
40
  | One Excel workbook | `tabalyst report sales.xlsx` | `sales.report.html` beside the source |
41
+ | Every table of a workbook | `tabalyst report shop.xlsx --all-collections` | `shop.costs.html`, `shop.sales.html`, ... beside the source |
40
42
  | From a scan document | `tabalyst report --scan data.scan.json` | `data.html` beside the scan |
41
43
 
42
44
  The simplest command keeps the source filename:
@@ -105,9 +107,17 @@ tabalyst report sales.xlsx
105
107
 
106
108
  Cells keep their Excel type, and dates are read as dates. A workbook with one
107
109
  table needs nothing else. When several sheets or tables are equally plausible,
108
- Tabalyst stops and lists them instead of choosing: pass `--collection Costs` (or
109
- `'$.Sales.Orders'` for a named table), or see [Tabalyst Inspect](#tabalyst-inspect).
110
- Older `.xls` files are not read: save them as `.xlsx`.
110
+ Tabalyst stops and prints one command per table to copy and run, such as
111
+ `tabalyst report shop.xlsx --collection Costs` (or `Sales.Orders` for a named
112
+ table), or see [Tabalyst Inspect](#tabalyst-inspect). To report every table at
113
+ once, use `--all-collections`:
114
+
115
+ ```console
116
+ tabalyst report shop.xlsx --all-collections -d reports
117
+ ```
118
+
119
+ This writes `shop.costs.html` and `shop.costs.json` for each table, named after
120
+ the file and the sheet. Older `.xls` files are not read: save them as `.xlsx`.
111
121
 
112
122
  ### Multiple files
113
123
 
@@ -226,6 +236,33 @@ Sampling reads CSV records as a stream. Random and stratified sampling keep
226
236
  only the requested sample, plus stratum counts, in memory. Existing outputs
227
237
  require `--force`, and an input file is never overwritten.
228
238
 
239
+ ## Tabalyst Generate
240
+
241
+ Generate synthetic CSV data from a versioned YAML definition. The packaged
242
+ `crm` example writes one contacts file; `insurance`
243
+ writes three branch files and a combined file:
244
+
245
+ ```console
246
+ tabalyst generate --example crm -o crm-contacts.csv
247
+ tabalyst generate --example insurance --rows 3000 -d insurance-output/
248
+ tabalyst generate --example insurance --rows 3000 --anomaly-profile dirty_realistic -d insurance-dirty/
249
+ ```
250
+
251
+ You can pass `examples/generate/input/crm.yaml` or
252
+ `examples/generate/input/insurance.yaml` instead of `--example`. A selected anomaly
253
+ profile writes an altered CSV, a cell change log and a summary beside the
254
+ untouched clean CSV. Insurance's profile changes only the combined file.
255
+ `--rows 3000` apportions 2,182, 273 and 545 rows to the three branches.
256
+ Existing outputs require `--force`.
257
+
258
+ Run `python examples/generate/generate_examples.py` to generate every YAML
259
+ from `examples/generate/input/` into ignored local output folders.
260
+
261
+ Insurance preserves the 33 source columns and the combined `filiale` column of
262
+ the existing public demo, but its generated rows are different. The branch date
263
+ and identifier formats are preserved; `filiale` is the last column in the new
264
+ combined CSV. The current public demo and report remain unchanged.
265
+
229
266
  ## Tabalyst Inspect
230
267
 
231
268
  A JSON file can hold several arrays, and an Excel workbook several sheets.
@@ -379,7 +416,9 @@ scan = tabalyst.scan("orders.json")
379
416
  scans = tabalyst.generate_scans(["data/*.json"], output_dir="scans")
380
417
 
381
418
  inspection = tabalyst.inspect("orders.json")
382
- inspections = tabalyst.generate_inspections(["data/*.json", "logs/*.jsonl", "*.xlsx"])
419
+ inspections = tabalyst.generate_inspections(
420
+ ["data/*.json", "logs/*.jsonl", "*.xlsx"]
421
+ )
383
422
  reports = tabalyst.generate_reports(["scans/*.scan.json"], from_scan=True)
384
423
  ```
385
424
 
@@ -465,8 +504,30 @@ Additional documentation:
465
504
  Please report defects and feature requests through the
466
505
  [GitHub issue tracker](https://github.com/loribel-labs/tabalyst/issues).
467
506
 
507
+ ## For AI assistants
508
+
509
+ To get help from an AI assistant, paste this prompt into a chat that can read
510
+ web pages:
511
+
512
+ ```text
513
+ Read the complete Tabalyst documentation at
514
+ https://docs.tabalyst.com/llms-full.txt, then help me use Tabalyst well.
515
+ Ask me what my data looks like and what I want to learn from it, then suggest
516
+ the right commands and options. Base your answers only on that documentation.
517
+ ```
518
+
519
+ If the assistant cannot open links, paste the content of the file instead.
520
+
521
+ The documentation site publishes plain-text versions of its pages:
522
+ [llms.txt](https://docs.tabalyst.com/llms.txt) lists every page with a
523
+ one-line description, and
524
+ [llms-full.txt](https://docs.tabalyst.com/llms-full.txt) holds the whole
525
+ documentation in one file. Every page is also available as Markdown by adding
526
+ `.md` to its URL, for example
527
+ [report.md](https://docs.tabalyst.com/report.md).
528
+
468
529
  ## License
469
530
 
470
531
  Tabalyst is released under the [Mozilla Public License 2.0](LICENSE).
471
532
 
472
- Created by Gregory Borelli — Catalyseur Numérique.
533
+ Created by Gregory Borelli. Open source project by Loribel Labs.
@@ -0,0 +1,144 @@
1
+ # Tabalyst examples
2
+
3
+ The examples are organized by input name so additional public datasets can be
4
+ added without mixing source data and generated artifacts.
5
+
6
+ ```text
7
+ examples/
8
+ |-- config.json
9
+ |-- generate/
10
+ | |-- generate_examples.py
11
+ | |-- input/ (four YAML definitions)
12
+ | `-- output/ (generated locally; ignored by Git)
13
+ |-- input/
14
+ | |-- basic.csv
15
+ | |-- insurance-customers.csv
16
+ | |-- orders.json
17
+ | |-- sales.xlsx
18
+ | `-- web-events.jsonl
19
+ `-- output/
20
+ |-- basic/
21
+ | |-- report.html
22
+ | |-- report.json
23
+ | `-- executions.json
24
+ |-- insurance-customers/
25
+ | |-- report.html
26
+ | |-- report.json
27
+ | |-- report/ (34 standalone column pages with --details)
28
+ | `-- executions.json
29
+ |-- orders/
30
+ | |-- report.html
31
+ | |-- report.json
32
+ | `-- executions.json
33
+ |-- sales/
34
+ | |-- report.html
35
+ | |-- report.json
36
+ | `-- executions.json
37
+ `-- web-events/
38
+ |-- report.html
39
+ |-- report.json
40
+ `-- executions.json
41
+ ```
42
+
43
+ `basic.csv` is a five-row smoke example. `insurance-customers.csv` contains 3,000
44
+ synthetic customer and contract records across 34 columns. Its people, contact
45
+ details, and contracts are fictional; email addresses use reserved `.example`
46
+ domains. `orders.json` is a synthetic shop export: 60 customers with nested
47
+ addresses, tags and orders. Its report has one dataset, `$.customers[]`, the only
48
+ collection of arrays of objects that Tabalyst Inspect finds in it.
49
+ `web-events.jsonl` is a synthetic web analytics log in JSON Lines: 300 events with
50
+ nested `user` and `device` objects, tags arrays, null values and an optional
51
+ field that appears late, plus three bad lines (a truncated record, a line that
52
+ is not JSON and a JSON array instead of an object). Its report is partial on
53
+ purpose: the three lines are excluded and counted, the 300 events are analyzed.
54
+ Its people and identifiers are fictional. `sales.xlsx` is a synthetic Excel
55
+ workbook: a sheet `Orders` of 120 fictional orders under two title lines, a
56
+ five-row `Regions` lookup sheet and a `Notes` sheet. Inspect selects `$.Orders`,
57
+ which has more than ten times the rows of `$.Regions`, so the report needs no
58
+ choice. The workbook is written by `make_sales_workbook.py`, with the same
59
+ bytes on every run; it needs `pip install xlsxwriter` and is only to run when the
60
+ input itself must be regenerated.
61
+
62
+ From the repository root, regenerate the outputs with:
63
+
64
+ ```console
65
+ tabalyst report examples/input/basic.csv -o examples/output/basic/report.html --config examples/config.json --force
66
+ tabalyst report examples/input/insurance-customers.csv -o examples/output/insurance-customers/report.html --config examples/config.json --details --force
67
+ tabalyst report examples/input/orders.json -o examples/output/orders/report.html --config examples/config.json --force
68
+ tabalyst report examples/input/web-events.jsonl -o examples/output/web-events/report.html --config examples/config.json --force
69
+ tabalyst report examples/input/sales.xlsx -o examples/output/sales/report.html --config examples/config.json --force
70
+ ```
71
+
72
+ Every output folder is independent and contains its own HTML report, canonical
73
+ JSON profile and cumulative `executions.json` history. The insurance example
74
+ also has standalone HTML pages under `report/`. Delete an output folder
75
+ before regenerating when a fresh one-entry execution history is required.
76
+
77
+ The input datasets and generated output are public repository examples. The wheel
78
+ contains the runtime package, report resources, and packaged copies of the
79
+ `examples/generate/input/` YAML definitions. CRM and Insurance are local development
80
+ demos. Generate is under development
81
+ and is not advertised as a released feature. The CRM data is synthetic, with
82
+ reserved `.example` domains and fictional phone numbers. The configured 20,000
83
+ rows, generic account names and CRM audit story were approved for the example
84
+ on 2026-10-05. Generate remains local development work until a release.
85
+
86
+ To review the CRM export outside the checkout:
87
+
88
+ ```console
89
+ tabalyst generate --example crm --rows 500 --anomaly-profile dirty_realistic -d OUTPUT_DIR
90
+ tabalyst scan OUTPUT_DIR/crm-contacts.dirty_realistic.csv -o OUTPUT_DIR/scan.json
91
+ tabalyst report OUTPUT_DIR/scan.json --scan -o OUTPUT_DIR/report.html
92
+ ```
93
+
94
+ Scan and Report expose missing values, fragmented categories and normalized
95
+ variants. The mutation summary counts requested, applied and skipped changes.
96
+ Neither tool currently diagnoses cross-column CRM rules such as stage versus
97
+ probability.
98
+
99
+ `examples/generate/input/common-references.yaml` is a lot 3R development fixture.
100
+ It generates coherent synthetic Canadian person, address and contact fields
101
+ from packaged references. Its prefixes and area codes are synthetic, email
102
+ domains end in `.example`, and phone line numbers use reserved `555-01xx`.
103
+ The Insurance example is now complete in `examples/generate/input/insurance.yaml`
104
+ and the packaged `--example insurance` copy. Its seven domain tables are in
105
+ `src/tabalyst/generate_resources/insurance/`; their provenance is in
106
+ `insurance/SOURCES.md`. To review 3,000 rows outside the checkout:
107
+
108
+ ```console
109
+ tabalyst generate --example insurance --rows 3000 --anomaly-profile dirty_realistic -d OUTPUT_DIR
110
+ ```
111
+
112
+ This creates clean files for Principal, Ontario and Quebec, their clean union,
113
+ and three anomaly artifacts for that union. The 2,182/273/545 branch allocation
114
+ comes from the declared 200,000/25,000/50,000 weights. The new clean CSV has
115
+ the same 34 column names as the existing public demo, but places `filiale` last
116
+ and has different rows and identifiers. Do not replace
117
+ `examples/input/insurance-customers.csv` or its report without a separate
118
+ decision to change the published demo.
119
+
120
+ `examples/generate/input/cohorts-anomalies.yaml` is a lot 4 development fixture. It
121
+ shows two normal status conventions in the clean CSV and a separate `dirty`
122
+ profile that writes an altered CSV, cell-level change log and summary. Run it
123
+ with `tabalyst generate examples/generate/input/cohorts-anomalies.yaml --anomaly-profile dirty -d OUTPUT_DIR`.
124
+ Keep `OUTPUT_DIR` outside the repository for one-off local trials. The packaged
125
+ Insurance YAML is the same complete example as the visible file.
126
+
127
+ ## Regenerate all Generate examples
128
+
129
+ Run the script from the repository root with an editable installation of
130
+ Tabalyst. It reads every YAML in `examples/generate/input/` and writes into
131
+ `examples/generate/output/<definition>/`. The output folder is ignored by Git.
132
+ By default it uses each YAML's declared row counts and generates every declared
133
+ anomaly profile along with its clean CSV. Insurance therefore writes 275,000
134
+ combined rows; allow enough disk space for the clean and altered copies.
135
+
136
+ ```console
137
+ python examples/generate/generate_examples.py
138
+ ```
139
+
140
+ Existing outputs are protected; pass `--force` to replace generated files.
141
+ For a quick review run, `--rows 30` overrides each definition's row count,
142
+ and `--output-dir PATH` writes outside the checkout. `--clean-only` skips the
143
+ profile passes. A definition with several profiles gets one subfolder per
144
+ profile so its clean files cannot collide.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tabalyst"
7
- version = "0.6.0"
7
+ version = "0.6.2"
8
8
  description = "Open-source toolkit for understanding and working with unfamiliar data."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -40,6 +40,7 @@ dependencies = [
40
40
  "platformdirs>=4",
41
41
  "pydantic>=2.11",
42
42
  "python-calamine>=0.8",
43
+ "PyYAML>=6.0",
43
44
  "typer>=0.12",
44
45
  ]
45
46
 
@@ -59,7 +60,7 @@ tabalyst = "tabalyst.cli:main"
59
60
  where = ["src"]
60
61
 
61
62
  [tool.setuptools.package-data]
62
- tabalyst = ["templates/*.html", "static/*.css", "static/*.js"]
63
+ tabalyst = ["templates/*.html", "static/*.css", "static/*.js", "generate_examples/*.yaml", "generate_resources/**/*.csv", "generate_resources/*.md"]
63
64
 
64
65
  [project.urls]
65
66
  Homepage = "https://github.com/loribel-labs/tabalyst"
@@ -6,6 +6,10 @@
6
6
 
7
7
  from tabalyst._version import __version__
8
8
  from tabalyst.errors import ConfigurationError, InputError, ReportError, TabalystError
9
+ from tabalyst.generate_service import GeneratePlan as GeneratePlan
10
+ from tabalyst.generate_service import GenerateResult as GenerateResult
11
+ from tabalyst.generate_service import build_generate_plan as build_generate_plan
12
+ from tabalyst.generate_service import generate_datasets as generate_datasets
9
13
  from tabalyst.inspect_service import InspectResult as InspectResult
10
14
  from tabalyst.inspect_service import generate_inspections as generate_inspections
11
15
  from tabalyst.inspect_service import inspect as inspect
@@ -26,6 +30,8 @@ from tabalyst.service import analyze_scan as analyze_scan
26
30
 
27
31
  __all__ = [
28
32
  "ConfigurationError",
33
+ "GeneratePlan",
34
+ "GenerateResult",
29
35
  "InputError",
30
36
  "InspectResult",
31
37
  "ReportError",
@@ -34,6 +40,8 @@ __all__ = [
34
40
  "TabalystError",
35
41
  "__version__",
36
42
  "analyze",
43
+ "build_generate_plan",
44
+ "generate_datasets",
37
45
  "generate_inspections",
38
46
  "generate_reports",
39
47
  "generate_samples",
@@ -90,6 +90,16 @@ def resolve_input_specs(input_specs: Sequence[str | Path]) -> list[Path]:
90
90
  return sources
91
91
 
92
92
 
93
+ def output_directory(output_dir: str | Path | None) -> Path | None:
94
+ """``--output-dir`` as a path, refused when it is an existing file."""
95
+ if output_dir is None:
96
+ return None
97
+ destination = Path(output_dir)
98
+ if destination.exists() and not destination.is_dir():
99
+ raise ReportError(f"Output directory path is a file: {destination}")
100
+ return destination
101
+
102
+
93
103
  def plan_outputs(
94
104
  input_specs: Sequence[str | Path],
95
105
  *,
@@ -108,9 +118,7 @@ def plan_outputs(
108
118
  if output is not None and len(sources) != 1:
109
119
  raise ConfigurationError("--output can only be used with one input file.")
110
120
 
111
- destination = Path(output_dir) if output_dir is not None else None
112
- if destination is not None and destination.exists() and not destination.is_dir():
113
- raise ReportError(f"Output directory path is a file: {destination}")
121
+ destination = output_directory(output_dir)
114
122
  return [
115
123
  (
116
124
  source,
@@ -10,6 +10,7 @@ import typer
10
10
 
11
11
  from tabalyst import __version__
12
12
  from tabalyst.cli.cache import cache_app
13
+ from tabalyst.cli.generate import generate_command
13
14
  from tabalyst.cli.inspect import inspect_command
14
15
  from tabalyst.cli.report import report_command
15
16
  from tabalyst.cli.sample import sample_command
@@ -45,6 +46,7 @@ def root(
45
46
 
46
47
 
47
48
  app.command("inspect")(inspect_command)
49
+ app.command("generate")(generate_command)
48
50
  app.command("report")(report_command)
49
51
  app.command("sample")(sample_command)
50
52
  app.command("scan")(scan_command)
@@ -0,0 +1,67 @@
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+
5
+ """CLI adapter for Tabalyst Generate."""
6
+
7
+ from pathlib import Path
8
+ from typing import Annotated
9
+
10
+ import typer
11
+
12
+ from tabalyst.cli.terminal import error_exit_code
13
+ from tabalyst.errors import TabalystError
14
+ from tabalyst.generate_service import build_generate_plan, generate_datasets
15
+
16
+
17
+ def generate_command(
18
+ definition: Annotated[
19
+ Path | None,
20
+ typer.Argument(help="YAML Generate definition."),
21
+ ] = None,
22
+ example: Annotated[
23
+ str | None,
24
+ typer.Option("--example", help="Packaged example: crm or insurance."),
25
+ ] = None,
26
+ output: Annotated[
27
+ Path | None,
28
+ typer.Option("--output", "-o", help="One clean CSV file."),
29
+ ] = None,
30
+ output_dir: Annotated[
31
+ Path | None,
32
+ typer.Option("--output-dir", "-d", help="Directory for all artifacts."),
33
+ ] = None,
34
+ rows: Annotated[
35
+ int | None,
36
+ typer.Option("--rows", help="Number of generated rows."),
37
+ ] = None,
38
+ seed: Annotated[
39
+ int | None,
40
+ typer.Option("--seed", help="Root random seed."),
41
+ ] = None,
42
+ as_of_date: Annotated[
43
+ str | None,
44
+ typer.Option("--as-of-date", help="Reference date (YYYY-MM-DD)."),
45
+ ] = None,
46
+ anomaly_profile: Annotated[
47
+ str | None,
48
+ typer.Option("--anomaly-profile", help="Selected anomaly profile."),
49
+ ] = None,
50
+ force: Annotated[
51
+ bool,
52
+ typer.Option("--force", "-f", help="Replace existing artifacts."),
53
+ ] = False,
54
+ ) -> None:
55
+ """Validate and generate datasets from a YAML definition."""
56
+ try:
57
+ plan = build_generate_plan(
58
+ definition, example=example, output=output, output_dir=output_dir,
59
+ rows=rows, seed=seed, as_of_date=as_of_date,
60
+ anomaly_profile=anomaly_profile, force=force,
61
+ )
62
+ result = generate_datasets(plan)
63
+ except TabalystError as exc:
64
+ typer.echo(f"Error: {exc}", err=True)
65
+ raise typer.Exit(error_exit_code(exc)) from exc
66
+ for path in result.paths:
67
+ typer.echo(f"Created: {path}", err=True)
@@ -17,6 +17,7 @@ from tabalyst.inspect_service import (
17
17
  InspectResult,
18
18
  generate_inspections,
19
19
  )
20
+ from tabalyst.inspector.choices import choice_lines
20
21
 
21
22
  _BASES = {
22
23
  "root_array": "the root array",
@@ -50,16 +51,12 @@ def _choices(success: InspectResult) -> list[str]:
50
51
  detection = success.document.detection
51
52
  if detection.selection.basis != "ambiguous":
52
53
  return []
53
- source = str(success.source)
54
- if " " in source:
55
- source = f'"{source}"'
56
- lines = ["Choose the collection to analyze:"]
57
- lines.extend(
58
- f" tabalyst report {source} --collection '{candidate.path}'"
59
- for candidate in detection.candidates
60
- if candidate.eligible
54
+ return choice_lines(
55
+ "report",
56
+ success.source,
57
+ [candidate.path for candidate in detection.candidates if candidate.eligible],
58
+ noun="table" if success.document.inspect.kind == "excel" else "collection",
61
59
  )
62
- return lines
63
60
 
64
61
 
65
62
  def _print_success(success: InspectResult, *, verbose: bool) -> None:
@@ -16,6 +16,7 @@ from tabalyst.cli.terminal import (
16
16
  batch_exit_code,
17
17
  collection_paths,
18
18
  error_exit_code,
19
+ error_text,
19
20
  )
20
21
  from tabalyst.errors import TabalystError
21
22
  from tabalyst.report_service import BatchReportResult, ReportSuccess, generate_reports
@@ -70,6 +71,8 @@ def _print_batch_result(
70
71
  details.append(f"delimiter {source['delimiter']!r}")
71
72
  typer.echo(" " + " | ".join(details), err=True)
72
73
 
74
+ for source, warning in batch.plan.warnings:
75
+ typer.echo(f"Warning [{source}]: {warning}", err=True)
73
76
  for success in batch.successes:
74
77
  for warning in success.warnings:
75
78
  typer.echo(f"Warning [{success.job.source}]: {warning}", err=True)
@@ -81,7 +84,10 @@ def _print_batch_result(
81
84
  err=True,
82
85
  )
83
86
  for failure in batch.failures:
84
- typer.echo(f"Error [{failure.job.source}]: {failure.error}", err=True)
87
+ typer.echo(
88
+ f"Error [{failure.job.source}]: {error_text(failure.error, 'report')}",
89
+ err=True,
90
+ )
85
91
 
86
92
  if len(batch.plan.jobs) > 1 and (not quiet or batch.failures):
87
93
  typer.echo(
@@ -96,8 +102,8 @@ def report_command(
96
102
  typer.Argument(
97
103
  metavar="INPUT...",
98
104
  help=(
99
- "One or more CSV or JSON files, or scan documents with --scan, "
100
- "or non-recursive glob patterns."
105
+ "One or more CSV, JSON, JSONL or Excel (.xlsx, .xlsm) files, or "
106
+ "scan documents with --scan, or non-recursive glob patterns."
101
107
  ),
102
108
  ),
103
109
  ],
@@ -114,7 +120,10 @@ def report_command(
114
120
  typer.Option(
115
121
  "--output-dir",
116
122
  "-d",
117
- help="Directory for reports named after their source files.",
123
+ help=(
124
+ "Directory for reports named after their source files; "
125
+ "required by --all-collections with several inputs."
126
+ ),
118
127
  ),
119
128
  ] = None,
120
129
  delimiter: Annotated[
@@ -133,6 +142,17 @@ def report_command(
133
142
  list[str] | None,
134
143
  typer.Option("--collection", help=COLLECTION_HELP),
135
144
  ] = None,
145
+ all_collections: Annotated[
146
+ bool,
147
+ typer.Option(
148
+ "--all-collections",
149
+ help=(
150
+ "Report every visible collection of a JSON file or Excel "
151
+ "workbook, as <name>.<collection>.html and .json; needs -d "
152
+ "with several inputs."
153
+ ),
154
+ ),
155
+ ] = False,
136
156
  from_scan: Annotated[
137
157
  bool,
138
158
  typer.Option(
@@ -202,9 +222,10 @@ def report_command(
202
222
  on_progress=progress,
203
223
  from_scan=from_scan,
204
224
  workers=workers,
225
+ all_collections=all_collections,
205
226
  )
206
227
  except TabalystError as exc:
207
- typer.echo(f"Error: {exc}", err=True)
228
+ typer.echo(f"Error: {error_text(exc, 'report')}", err=True)
208
229
  raise typer.Exit(error_exit_code(exc)) from exc
209
230
 
210
231
  _print_batch_result(batch, quiet=quiet, verbose=verbose)