openhound 0.1.3.dev2__tar.gz → 0.1.4__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 (150) hide show
  1. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/workflows/test.yml +8 -0
  2. {openhound-0.1.3.dev2 → openhound-0.1.4}/PKG-INFO +11 -10
  3. {openhound-0.1.3.dev2 → openhound-0.1.4}/pyproject.toml +15 -14
  4. openhound-0.1.4/src/openhound/core/lookup.py +56 -0
  5. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/preproc.py +34 -0
  6. openhound-0.1.4/tests/test_lookup.py +41 -0
  7. openhound-0.1.4/tests/test_preproc.py +93 -0
  8. {openhound-0.1.3.dev2 → openhound-0.1.4}/uv.lock +366 -300
  9. openhound-0.1.3.dev2/src/openhound/core/lookup.py +0 -33
  10. openhound-0.1.3.dev2/tests/test_preproc_default_lookup.py +0 -34
  11. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/CODE_OF_CONDUCT.md +0 -0
  12. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/CONTRIBUTING.md +0 -0
  13. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/GOVERNANCE.md +0 -0
  14. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/GitHub-COC-Header.png +0 -0
  15. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/GitHub-CONTRIB-Header.png +0 -0
  16. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/GitHub-GOV-Header.png +0 -0
  17. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/GitHub-Header.png +0 -0
  18. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/GitHub-LIC-Header.png +0 -0
  19. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/GitHub-SEC-Header.png +0 -0
  20. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/OpenHoundDark.svg +0 -0
  21. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/OpenHoundLight.svg +0 -0
  22. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/SECURITY.md +0 -0
  23. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/SO-Dark.svg +0 -0
  24. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/SO-FOSS-Dark.svg +0 -0
  25. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/SO-FOSS-Light.svg +0 -0
  26. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/SO-Light.svg +0 -0
  27. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/workflows/build-and-publish.yml +0 -0
  28. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/workflows/build-and-sign-container.yml +0 -0
  29. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/workflows/release-on-merge.yml +0 -0
  30. {openhound-0.1.3.dev2 → openhound-0.1.4}/.github/workflows/validate-branch.yml +0 -0
  31. {openhound-0.1.3.dev2 → openhound-0.1.4}/.gitignore +0 -0
  32. {openhound-0.1.3.dev2 → openhound-0.1.4}/.pre-commit-config.yaml +0 -0
  33. {openhound-0.1.3.dev2 → openhound-0.1.4}/Dockerfile +0 -0
  34. {openhound-0.1.3.dev2 → openhound-0.1.4}/LICENSE.md +0 -0
  35. {openhound-0.1.3.dev2 → openhound-0.1.4}/README.md +0 -0
  36. {openhound-0.1.3.dev2 → openhound-0.1.4}/deployments/helm/openhound/Chart.yaml +0 -0
  37. {openhound-0.1.3.dev2 → openhound-0.1.4}/deployments/helm/openhound/README.md +0 -0
  38. {openhound-0.1.3.dev2 → openhound-0.1.4}/deployments/helm/openhound/templates/_helpers.tpl +0 -0
  39. {openhound-0.1.3.dev2 → openhound-0.1.4}/deployments/helm/openhound/templates/deployment.yaml +0 -0
  40. {openhound-0.1.3.dev2 → openhound-0.1.4}/deployments/helm/openhound/templates/serviceaccount.yaml +0 -0
  41. {openhound-0.1.3.dev2 → openhound-0.1.4}/deployments/helm/openhound/values.yaml +0 -0
  42. {openhound-0.1.3.dev2 → openhound-0.1.4}/deployments/helm/values.example.yaml +0 -0
  43. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-community/.dlt-example/config.toml +0 -0
  44. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-community/.dlt-example/secrets_github.toml +0 -0
  45. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-community/.dlt-example/secrets_jamf.toml +0 -0
  46. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-community/.dlt-example/secrets_okta.toml +0 -0
  47. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-community/README.md +0 -0
  48. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-community/docker-compose.yml +0 -0
  49. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-enterprise/.dlt-example/config.toml +0 -0
  50. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-enterprise/.dlt-example/secrets_github.toml +0 -0
  51. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-enterprise/.dlt-example/secrets_jamf.toml +0 -0
  52. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-enterprise/.dlt-example/secrets_okta.toml +0 -0
  53. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-enterprise/README.md +0 -0
  54. {openhound-0.1.3.dev2 → openhound-0.1.4}/example-configurations/bloodhound-enterprise/docker-compose.yml +0 -0
  55. {openhound-0.1.3.dev2 → openhound-0.1.4}/justfile +0 -0
  56. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/__init__.py +0 -0
  57. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/__main__.py +0 -0
  58. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/cli/__init__.py +0 -0
  59. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/cli/collect.py +0 -0
  60. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/cli/convert.py +0 -0
  61. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/cli/create.py +0 -0
  62. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/cli/override.py +0 -0
  63. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/cli/preproc.py +0 -0
  64. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/cli/privilege_zone.py +0 -0
  65. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/cli/saved_search.py +0 -0
  66. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/__init__.py +0 -0
  67. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/app.py +0 -0
  68. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/app.pyi +0 -0
  69. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/asset.py +0 -0
  70. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/clients/__init__.py +0 -0
  71. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/clients/bloodhound.py +0 -0
  72. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/clients/bloodhound_enterprise.py +0 -0
  73. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/clients/models/__init__.py +0 -0
  74. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/clients/models/asset_groups.py +0 -0
  75. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/clients/models/custom_nodes.py +0 -0
  76. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/clients/models/graph_cypher.py +0 -0
  77. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/clients/models/jobs.py +0 -0
  78. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/clients/models/saved_query.py +0 -0
  79. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/collect.py +0 -0
  80. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/context.py +0 -0
  81. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/convert.py +0 -0
  82. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/exceptions.py +0 -0
  83. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/logging.py +0 -0
  84. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/manager.py +0 -0
  85. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/models/__init__.py +0 -0
  86. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/models/collector.py +0 -0
  87. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/models/entries.py +0 -0
  88. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/models/entries_dataclass.py +0 -0
  89. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/models/extension.py +0 -0
  90. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/models/graph.py +0 -0
  91. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/models/icons.py +0 -0
  92. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/models/privilege_zone.py +0 -0
  93. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/models/saved_search.py +0 -0
  94. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/pipeline.py +0 -0
  95. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/privilege_zones.py +0 -0
  96. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/progress.py +0 -0
  97. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/resources.py +0 -0
  98. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/core/saved_searches.py +0 -0
  99. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/destinations/__init__.py +0 -0
  100. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/destinations/bloodhound/__init__.py +0 -0
  101. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/destinations/bloodhound/destination.py +0 -0
  102. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/destinations/bloodhound_enterprise/__init__.py +0 -0
  103. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/destinations/bloodhound_enterprise/destination.py +0 -0
  104. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/destinations/opengraph/__init__.py +0 -0
  105. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/destinations/opengraph/destination.py +0 -0
  106. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/docs/__init__.py +0 -0
  107. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/docs/pipeline.py +0 -0
  108. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/docs/templates/asset.md.j2 +0 -0
  109. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/docs/templates/class_table.md.j2 +0 -0
  110. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/docs/templates/edge.md.j2 +0 -0
  111. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/docs/templates/node.md.j2 +0 -0
  112. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/docs/templates/overview.md.j2 +0 -0
  113. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/docs/templates/pipeline.md.j2 +0 -0
  114. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/docs/templates/test +0 -0
  115. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/main.py +0 -0
  116. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/scheduler/__init__.py +0 -0
  117. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/scheduler/dataflow.py +0 -0
  118. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/scheduler/service.py +0 -0
  119. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/sources/__init__.py +0 -0
  120. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/sources/bloodhound_config/__init__.py +0 -0
  121. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/sources/bloodhound_config/source.py +0 -0
  122. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/sources/opengraph/__init__.py +0 -0
  123. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/sources/opengraph/entries.py +0 -0
  124. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/sources/opengraph/guid.py +0 -0
  125. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/sources/opengraph/source.py +0 -0
  126. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/sources/resource_files/__init__.py +0 -0
  127. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/openhound/sources/resource_files/source.py +0 -0
  128. {openhound-0.1.3.dev2 → openhound-0.1.4}/src/scheduler.py +0 -0
  129. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/__init__.py +0 -0
  130. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/conftest.py +0 -0
  131. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_bhe_job_scheduling.py +0 -0
  132. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_collect_output_dir.py +0 -0
  133. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_convert_lookup_file.py +0 -0
  134. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_convert_output_dir.py +0 -0
  135. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_create_docs.py +0 -0
  136. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_data/api/jobs/job_end.json +0 -0
  137. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_data/api/jobs/job_start.json +0 -0
  138. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_data/api/jobs/jobs_available_empty.json +0 -0
  139. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_data/api/jobs/jobs_available_with_job.json +0 -0
  140. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_data/api/jobs/jobs_current.json +0 -0
  141. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_data/api/upload/upload_start.json +0 -0
  142. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_data/extensions/pz_selectors/jamf_tenant.json +0 -0
  143. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_data/extensions/saved_searches/jamf_query_by_name.json +0 -0
  144. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_data/sample_graph.json +0 -0
  145. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_docs_pipeline.py +0 -0
  146. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_extensions_format.py +0 -0
  147. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_log_handlers.py +0 -0
  148. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_pz_selector_sync.py +0 -0
  149. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_resource_def.py +0 -0
  150. {openhound-0.1.3.dev2 → openhound-0.1.4}/tests/test_saved_searches_sync.py +0 -0
@@ -59,3 +59,11 @@ jobs:
59
59
  - name: Run BHE job scheduling test
60
60
  run: |
61
61
  .venv/bin/pytest tests/test_bhe_job_scheduling.py -v
62
+
63
+ - name: Run preprocess lookup generation tests
64
+ run: |
65
+ .venv/bin/pytest tests/test_preproc.py -v
66
+
67
+ - name: Run DuckDB lookup exception handling tests
68
+ run: |
69
+ .venv/bin/pytest tests/test_lookup.py -v
@@ -1,32 +1,33 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhound
3
- Version: 0.1.3.dev2
3
+ Version: 0.1.4
4
4
  Summary: OpenGraph collector framework for BloodHound
5
5
  License-File: LICENSE.md
6
6
  Requires-Python: >=3.13
7
7
  Requires-Dist: alive-progress>=3.3.0
8
8
  Requires-Dist: cookiecutter>=2.6.0
9
- Requires-Dist: dlt==1.22.2
10
- Requires-Dist: duckdb==1.5.0
9
+ Requires-Dist: dlt==1.26.0
10
+ Requires-Dist: duckdb==1.5.2
11
11
  Requires-Dist: griffe-fieldz>=0.5.0
12
12
  Requires-Dist: griffe>=1.15.0
13
13
  Requires-Dist: jinja2>=3.1.6
14
14
  Requires-Dist: mkdocstrings[python]>=1.0.0
15
15
  Requires-Dist: psutil>=7.2.1
16
- Requires-Dist: pydantic-extra-types>=2.11.0
17
- Requires-Dist: pydantic==2.12.5
16
+ Requires-Dist: pydantic-extra-types>=2.11.1
17
+ Requires-Dist: pydantic==2.13.3
18
18
  Requires-Dist: tqdm>=4.67.1
19
- Requires-Dist: typer>=0.19.2
19
+ Requires-Dist: typer>=0.25.1
20
+ Requires-Dist: types-requests==2.33.0.20260503
20
21
  Provides-Extra: all
21
22
  Requires-Dist: openhound-github==0.1.0; extra == 'all'
22
- Requires-Dist: openhound-jamf==0.1.0; extra == 'all'
23
- Requires-Dist: openhound-okta==0.1.1; extra == 'all'
23
+ Requires-Dist: openhound-jamf==0.1.3; extra == 'all'
24
+ Requires-Dist: openhound-okta==0.1.2; extra == 'all'
24
25
  Provides-Extra: github
25
26
  Requires-Dist: openhound-github==0.1.0; extra == 'github'
26
27
  Provides-Extra: jamf
27
- Requires-Dist: openhound-jamf==0.1.0; extra == 'jamf'
28
+ Requires-Dist: openhound-jamf==0.1.3; extra == 'jamf'
28
29
  Provides-Extra: okta
29
- Requires-Dist: openhound-okta==0.1.1; extra == 'okta'
30
+ Requires-Dist: openhound-okta==0.1.2; extra == 'okta'
30
31
  Description-Content-Type: text/markdown
31
32
 
32
33
  <p align="center">
@@ -6,35 +6,36 @@ readme = "README.md"
6
6
  requires-python = ">=3.13"
7
7
  dependencies = [
8
8
  "alive-progress>=3.3.0",
9
- "dlt==1.22.2",
10
- "duckdb==1.5.0",
9
+ "dlt==1.26.0",
10
+ "duckdb==1.5.2",
11
11
  "griffe>=1.15.0",
12
12
  "griffe-fieldz>=0.5.0",
13
13
  "mkdocstrings[python]>=1.0.0",
14
14
  "psutil>=7.2.1",
15
- "pydantic==2.12.5",
15
+ "pydantic==2.13.3",
16
16
  "tqdm>=4.67.1",
17
- "typer>=0.19.2",
17
+ "typer>=0.25.1",
18
18
  "cookiecutter>=2.6.0",
19
- "pydantic-extra-types>=2.11.0",
20
19
  "jinja2>=3.1.6",
20
+ "types-requests==2.33.0.20260503",
21
+ "pydantic-extra-types>=2.11.1",
21
22
  ]
22
23
 
23
24
  [project.optional-dependencies]
24
25
  all = [
25
- "openhound-jamf==0.1.0",
26
+ "openhound-jamf==0.1.3",
26
27
  "openhound-github==0.1.0",
27
- "openhound-okta==0.1.1",
28
+ "openhound-okta==0.1.2",
28
29
  ]
29
30
  jamf = [
30
- "openhound-jamf==0.1.0",
31
+ "openhound-jamf==0.1.3",
31
32
  ]
32
33
  github = [
33
34
  "openhound-github==0.1.0"
34
35
  ]
35
36
 
36
37
  okta = [
37
- "openhound-okta==0.1.1",
38
+ "openhound-okta==0.1.2",
38
39
  ]
39
40
 
40
41
  [project.scripts]
@@ -66,13 +67,13 @@ local_scheme = "no-local-version"
66
67
  [dependency-groups]
67
68
  dev = [
68
69
  "openhound-faker==0.0.6",
69
- "ipython>=9.12.0",
70
+ "ipython>=9.13.0",
70
71
  "pre-commit>=4.5.1",
71
72
  "pytest>=9.0.1",
72
- "marimo>=0.23.0",
73
- "altair>=6.0.0",
74
- "fastapi>=0.129.0",
75
- "zensical>=0.0.23",
73
+ "marimo>=0.23.5",
74
+ "altair>=6.1.0",
75
+ "fastapi>=0.136.1",
76
+ "zensical>=0.0.40",
76
77
  "ruff>=0.15.4",
77
78
  "mypy>=1.19.1",
78
79
  "types-pyyaml>=6.0.12.20250915",
@@ -0,0 +1,56 @@
1
+ import logging
2
+
3
+ import duckdb
4
+ from duckdb import DuckDBPyConnection
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class LookupManager:
10
+ def __init__(self, client: DuckDBPyConnection, schema: str):
11
+ """Create a DuckDB lookup helper bound to specific schema.
12
+
13
+ Args:
14
+ client (DuckDBPyConnection): DuckDB connection used for queries.
15
+ schema (str): Schema name containing lookup tables.
16
+ """
17
+ self.schema = schema
18
+ self.client = client
19
+
20
+ def _find_all_objects(self, *args) -> list:
21
+ """Execute a query and return all rows.
22
+
23
+ Returns:
24
+ list: Query result rows as a list of tuples.
25
+ """
26
+ try:
27
+ self.client.execute(*args)
28
+ results = self.client.fetchall()
29
+ return results
30
+
31
+ except duckdb.CatalogException as err:
32
+ logger.error("DuckDB lookup failed, missing table: %s", err)
33
+ return []
34
+
35
+ except duckdb.Error as err:
36
+ logger.error("DuckDB lookup query failed: %s", err)
37
+ return []
38
+
39
+ def _find_single_object(self, *args) -> str | None:
40
+ """Execute a query and return the ID of the matching row
41
+
42
+ Returns:
43
+ str | None: The first column (ie. ID) value as a string or None if no result is found
44
+ """
45
+ try:
46
+ self.client.execute(*args)
47
+ result = self.client.fetchone()
48
+ return str(result[0]) if result else None
49
+
50
+ except duckdb.CatalogException as err:
51
+ logger.error("DuckDB lookup failed, missing table: %s", err)
52
+ return None
53
+
54
+ except duckdb.Error as err:
55
+ logger.error("DuckDB lookup query failed: %s", err)
56
+ return None
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from dataclasses import dataclass
2
3
  from pathlib import Path
3
4
  from typing import Callable
@@ -12,6 +13,33 @@ from openhound.core.pipeline import BasePipeline
12
13
  from openhound.core.progress import Progress
13
14
  from openhound.sources.resource_files.source import resource_files
14
15
 
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def run_transform(
20
+ transform: Callable[..., None],
21
+ con: duckdb.DuckDBPyConnection,
22
+ *args,
23
+ **kwargs,
24
+ ) -> None:
25
+ """A transformer helper function that handles DuckDB exceptions when generating a lookup"""
26
+ try:
27
+ transform(con, *args, **kwargs)
28
+
29
+ except duckdb.CatalogException as err:
30
+ logger.error(
31
+ "DuckDB preprocessing transform '%s' failed due to missing table: %s",
32
+ transform.__name__,
33
+ err,
34
+ )
35
+
36
+ except duckdb.Error as err:
37
+ logger.error(
38
+ "DuckDB preprocessing transform '%s' failed: %s",
39
+ transform.__name__,
40
+ err,
41
+ )
42
+
15
43
 
16
44
  class PreProcessor(BasePipeline):
17
45
  def __init__(
@@ -66,6 +94,12 @@ class PreProcessor(BasePipeline):
66
94
  con = duckdb.connect(str(self.output_file))
67
95
  try:
68
96
  self.transformer(con)
97
+ except duckdb.CatalogException as err:
98
+ logger.error(
99
+ "DuckDB preprocessing failed due to missing table: %s", err
100
+ )
101
+ except duckdb.Error as err:
102
+ logger.error("DuckDB preprocessing failed: %s", err)
69
103
  finally:
70
104
  con.close()
71
105
 
@@ -0,0 +1,41 @@
1
+ import logging
2
+
3
+ import duckdb
4
+
5
+ from openhound.core.lookup import LookupManager
6
+
7
+
8
+ def test_find_single_object_returns_none_on_duckdb_error(caplog):
9
+ client = duckdb.connect(":memory:")
10
+ lookup = LookupManager(client, "main")
11
+ caplog.set_level(logging.ERROR, logger="openhound.core.lookup")
12
+
13
+ try:
14
+ result = lookup._find_single_object("SELECT id FROM missing_table")
15
+ finally:
16
+ client.close()
17
+
18
+ assert result is None
19
+ assert any(
20
+ "DuckDB lookup failed, missing table:" in record.message
21
+ and "missing_table" in record.message
22
+ for record in caplog.records
23
+ )
24
+
25
+
26
+ def test_find_all_objects_returns_empty_list_on_duckdb_error(caplog):
27
+ client = duckdb.connect(":memory:")
28
+ lookup = LookupManager(client, "main")
29
+ caplog.set_level(logging.ERROR, logger="openhound.core.lookup")
30
+
31
+ try:
32
+ result = lookup._find_all_objects("SELECT id FROM missing_table")
33
+ finally:
34
+ client.close()
35
+
36
+ assert result == []
37
+ assert any(
38
+ "DuckDB lookup failed, missing table:" in record.message
39
+ and "missing_table" in record.message
40
+ for record in caplog.records
41
+ )
@@ -0,0 +1,93 @@
1
+ import logging
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import duckdb
6
+
7
+ os.environ["RUNTIME__LOG_PATH"] = "/tmp/openhound-test-logs"
8
+
9
+ from openhound.core.app import DEFAULT_LOOKUP_FILE, OpenHound
10
+ from openhound.core.preproc import PreProcessor, run_transform
11
+ from openhound.core.progress import Progress
12
+
13
+
14
+ def test_preproc_uses_default_lookup_file(monkeypatch, tmp_path):
15
+ captured: dict[str, Path] = {}
16
+
17
+ def fake_run(self, resources, filters=None):
18
+ captured["output_file"] = self.output_file
19
+ captured["resources"] = resources
20
+ return "ok"
21
+
22
+ monkeypatch.setattr(PreProcessor, "run", fake_run)
23
+
24
+ app = OpenHound("test", "test")
25
+
26
+ @app.preproc()
27
+ def preprocess(ctx):
28
+ return {"resource": "resource"}
29
+
30
+ result = app.preprocessor( # type: ignore[misc]
31
+ input_path=tmp_path,
32
+ progress=Progress.log,
33
+ )
34
+
35
+ assert result == "ok"
36
+ assert captured["output_file"] == DEFAULT_LOOKUP_FILE
37
+ assert captured["resources"] == {"resource": "resource"}
38
+
39
+
40
+ def test_preproc_logs_duckdb_transform_errors(monkeypatch, tmp_path, caplog):
41
+ def fake_run(self, source, **kwargs):
42
+ return "ok"
43
+
44
+ def missing_table_transform(con: duckdb.DuckDBPyConnection):
45
+ con.execute("SELECT * FROM missing_table")
46
+
47
+ monkeypatch.setattr(PreProcessor, "_run", fake_run)
48
+ caplog.set_level(logging.ERROR, logger="openhound.core.preproc")
49
+
50
+ preprocessor = PreProcessor(
51
+ name="test",
52
+ input_path=tmp_path,
53
+ output_file=tmp_path / "lookup.duckdb",
54
+ transformer=missing_table_transform,
55
+ )
56
+
57
+ result = preprocessor.run(resources={"resource": "resource"})
58
+
59
+ assert result == "ok"
60
+ assert any(
61
+ "DuckDB preprocessing failed due to missing table:" in record.message
62
+ and "missing_table" in record.message
63
+ for record in caplog.records
64
+ )
65
+
66
+
67
+ def test_run_transform_logs_transform_name_and_continues(caplog):
68
+ called: list[str] = []
69
+
70
+ def missing_table_transform(con: duckdb.DuckDBPyConnection):
71
+ called.append("missing")
72
+ con.execute("SELECT * FROM missing_table")
73
+
74
+ def successful_transform(con: duckdb.DuckDBPyConnection):
75
+ called.append("successful")
76
+ con.execute("SELECT 1")
77
+
78
+ con = duckdb.connect(":memory:")
79
+ caplog.set_level(logging.ERROR, logger="openhound.core.preproc")
80
+
81
+ try:
82
+ run_transform(missing_table_transform, con)
83
+ run_transform(successful_transform, con)
84
+ finally:
85
+ con.close()
86
+
87
+ assert called == ["missing", "successful"]
88
+ assert any(
89
+ "DuckDB preprocessing transform 'missing_table_transform' failed due to missing table:"
90
+ in record.message
91
+ and "missing_table" in record.message
92
+ for record in caplog.records
93
+ )