regscale-cli 6.16.0.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.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/__init__.py +1 -0
- regscale/airflow/__init__.py +9 -0
- regscale/airflow/azure/__init__.py +9 -0
- regscale/airflow/azure/cli.py +89 -0
- regscale/airflow/azure/upload_dags.py +116 -0
- regscale/airflow/click_dags.py +127 -0
- regscale/airflow/click_mixins.py +82 -0
- regscale/airflow/config.py +25 -0
- regscale/airflow/factories/__init__.py +0 -0
- regscale/airflow/factories/connections.py +58 -0
- regscale/airflow/factories/workflows.py +78 -0
- regscale/airflow/hierarchy.py +88 -0
- regscale/airflow/operators/__init__.py +0 -0
- regscale/airflow/operators/click.py +36 -0
- regscale/airflow/sensors/__init__.py +0 -0
- regscale/airflow/sensors/sql.py +107 -0
- regscale/airflow/sessions/__init__.py +0 -0
- regscale/airflow/sessions/sql/__init__.py +3 -0
- regscale/airflow/sessions/sql/queries.py +64 -0
- regscale/airflow/sessions/sql/sql_server_queries.py +248 -0
- regscale/airflow/tasks/__init__.py +0 -0
- regscale/airflow/tasks/branches.py +22 -0
- regscale/airflow/tasks/cli.py +116 -0
- regscale/airflow/tasks/click.py +73 -0
- regscale/airflow/tasks/debugging.py +9 -0
- regscale/airflow/tasks/groups.py +116 -0
- regscale/airflow/tasks/init.py +60 -0
- regscale/airflow/tasks/states.py +47 -0
- regscale/airflow/tasks/workflows.py +36 -0
- regscale/ansible/__init__.py +9 -0
- regscale/core/__init__.py +0 -0
- regscale/core/app/__init__.py +3 -0
- regscale/core/app/api.py +571 -0
- regscale/core/app/application.py +665 -0
- regscale/core/app/internal/__init__.py +136 -0
- regscale/core/app/internal/admin_actions.py +230 -0
- regscale/core/app/internal/assessments_editor.py +873 -0
- regscale/core/app/internal/catalog.py +316 -0
- regscale/core/app/internal/comparison.py +459 -0
- regscale/core/app/internal/control_editor.py +571 -0
- regscale/core/app/internal/encrypt.py +79 -0
- regscale/core/app/internal/evidence.py +1240 -0
- regscale/core/app/internal/file_uploads.py +151 -0
- regscale/core/app/internal/healthcheck.py +66 -0
- regscale/core/app/internal/login.py +305 -0
- regscale/core/app/internal/migrations.py +240 -0
- regscale/core/app/internal/model_editor.py +1701 -0
- regscale/core/app/internal/poam_editor.py +632 -0
- regscale/core/app/internal/workflow.py +105 -0
- regscale/core/app/logz.py +74 -0
- regscale/core/app/utils/XMLIR.py +258 -0
- regscale/core/app/utils/__init__.py +0 -0
- regscale/core/app/utils/api_handler.py +358 -0
- regscale/core/app/utils/app_utils.py +1110 -0
- regscale/core/app/utils/catalog_utils/__init__.py +0 -0
- regscale/core/app/utils/catalog_utils/common.py +91 -0
- regscale/core/app/utils/catalog_utils/compare_catalog.py +193 -0
- regscale/core/app/utils/catalog_utils/diagnostic_catalog.py +97 -0
- regscale/core/app/utils/catalog_utils/download_catalog.py +103 -0
- regscale/core/app/utils/catalog_utils/update_catalog.py +718 -0
- regscale/core/app/utils/catalog_utils/update_catalog_v2.py +1378 -0
- regscale/core/app/utils/catalog_utils/update_catalog_v3.py +1272 -0
- regscale/core/app/utils/catalog_utils/update_plans.py +334 -0
- regscale/core/app/utils/file_utils.py +238 -0
- regscale/core/app/utils/parser_utils.py +81 -0
- regscale/core/app/utils/pickle_file_handler.py +57 -0
- regscale/core/app/utils/regscale_utils.py +319 -0
- regscale/core/app/utils/report_utils.py +119 -0
- regscale/core/app/utils/variables.py +226 -0
- regscale/core/decorators.py +31 -0
- regscale/core/lazy_group.py +65 -0
- regscale/core/login.py +63 -0
- regscale/core/server/__init__.py +0 -0
- regscale/core/server/flask_api.py +473 -0
- regscale/core/server/helpers.py +373 -0
- regscale/core/server/rest.py +64 -0
- regscale/core/server/static/css/bootstrap.css +6030 -0
- regscale/core/server/static/css/bootstrap.min.css +6 -0
- regscale/core/server/static/css/main.css +176 -0
- regscale/core/server/static/images/regscale-cli.svg +49 -0
- regscale/core/server/static/images/regscale.svg +38 -0
- regscale/core/server/templates/base.html +74 -0
- regscale/core/server/templates/index.html +43 -0
- regscale/core/server/templates/login.html +28 -0
- regscale/core/server/templates/make_base64.html +22 -0
- regscale/core/server/templates/upload_STIG.html +109 -0
- regscale/core/server/templates/upload_STIG_result.html +26 -0
- regscale/core/server/templates/upload_ssp.html +144 -0
- regscale/core/server/templates/upload_ssp_result.html +128 -0
- regscale/core/static/__init__.py +0 -0
- regscale/core/static/regex.py +14 -0
- regscale/core/utils/__init__.py +117 -0
- regscale/core/utils/click_utils.py +13 -0
- regscale/core/utils/date.py +238 -0
- regscale/core/utils/graphql.py +254 -0
- regscale/core/utils/urls.py +23 -0
- regscale/dev/__init__.py +6 -0
- regscale/dev/analysis.py +454 -0
- regscale/dev/cli.py +235 -0
- regscale/dev/code_gen.py +492 -0
- regscale/dev/dirs.py +69 -0
- regscale/dev/docs.py +384 -0
- regscale/dev/monitoring.py +26 -0
- regscale/dev/profiling.py +216 -0
- regscale/exceptions/__init__.py +4 -0
- regscale/exceptions/license_exception.py +7 -0
- regscale/exceptions/validation_exception.py +9 -0
- regscale/integrations/__init__.py +1 -0
- regscale/integrations/commercial/__init__.py +486 -0
- regscale/integrations/commercial/ad.py +433 -0
- regscale/integrations/commercial/amazon/__init__.py +0 -0
- regscale/integrations/commercial/amazon/common.py +106 -0
- regscale/integrations/commercial/aqua/__init__.py +0 -0
- regscale/integrations/commercial/aqua/aqua.py +91 -0
- regscale/integrations/commercial/aws/__init__.py +6 -0
- regscale/integrations/commercial/aws/cli.py +322 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +110 -0
- regscale/integrations/commercial/aws/inventory/base.py +64 -0
- regscale/integrations/commercial/aws/inventory/resources/__init__.py +19 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +234 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +113 -0
- regscale/integrations/commercial/aws/inventory/resources/database.py +101 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +237 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +253 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +240 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +91 -0
- regscale/integrations/commercial/aws/scanner.py +823 -0
- regscale/integrations/commercial/azure/__init__.py +0 -0
- regscale/integrations/commercial/azure/common.py +32 -0
- regscale/integrations/commercial/azure/intune.py +488 -0
- regscale/integrations/commercial/azure/scanner.py +49 -0
- regscale/integrations/commercial/burp.py +78 -0
- regscale/integrations/commercial/cpe.py +144 -0
- regscale/integrations/commercial/crowdstrike.py +1117 -0
- regscale/integrations/commercial/defender.py +1511 -0
- regscale/integrations/commercial/dependabot.py +210 -0
- regscale/integrations/commercial/durosuite/__init__.py +0 -0
- regscale/integrations/commercial/durosuite/api.py +1546 -0
- regscale/integrations/commercial/durosuite/process_devices.py +101 -0
- regscale/integrations/commercial/durosuite/scanner.py +637 -0
- regscale/integrations/commercial/durosuite/variables.py +21 -0
- regscale/integrations/commercial/ecr.py +90 -0
- regscale/integrations/commercial/gcp/__init__.py +237 -0
- regscale/integrations/commercial/gcp/auth.py +96 -0
- regscale/integrations/commercial/gcp/control_tests.py +238 -0
- regscale/integrations/commercial/gcp/variables.py +18 -0
- regscale/integrations/commercial/gitlab.py +332 -0
- regscale/integrations/commercial/grype.py +165 -0
- regscale/integrations/commercial/ibm.py +90 -0
- regscale/integrations/commercial/import_all/__init__.py +0 -0
- regscale/integrations/commercial/import_all/import_all_cmd.py +467 -0
- regscale/integrations/commercial/import_all/scan_file_fingerprints.json +27 -0
- regscale/integrations/commercial/jira.py +1046 -0
- regscale/integrations/commercial/mappings/__init__.py +0 -0
- regscale/integrations/commercial/mappings/csf_controls.json +713 -0
- regscale/integrations/commercial/mappings/nist_800_53_r5_controls.json +1516 -0
- regscale/integrations/commercial/nessus/__init__.py +0 -0
- regscale/integrations/commercial/nessus/nessus_utils.py +429 -0
- regscale/integrations/commercial/nessus/scanner.py +416 -0
- regscale/integrations/commercial/nexpose.py +90 -0
- regscale/integrations/commercial/okta.py +798 -0
- regscale/integrations/commercial/opentext/__init__.py +0 -0
- regscale/integrations/commercial/opentext/click.py +99 -0
- regscale/integrations/commercial/opentext/scanner.py +143 -0
- regscale/integrations/commercial/prisma.py +91 -0
- regscale/integrations/commercial/qualys.py +1462 -0
- regscale/integrations/commercial/salesforce.py +980 -0
- regscale/integrations/commercial/sap/__init__.py +0 -0
- regscale/integrations/commercial/sap/click.py +31 -0
- regscale/integrations/commercial/sap/sysdig/__init__.py +0 -0
- regscale/integrations/commercial/sap/sysdig/click.py +57 -0
- regscale/integrations/commercial/sap/sysdig/sysdig_scanner.py +190 -0
- regscale/integrations/commercial/sap/tenable/__init__.py +0 -0
- regscale/integrations/commercial/sap/tenable/click.py +49 -0
- regscale/integrations/commercial/sap/tenable/scanner.py +196 -0
- regscale/integrations/commercial/servicenow.py +1756 -0
- regscale/integrations/commercial/sicura/__init__.py +0 -0
- regscale/integrations/commercial/sicura/api.py +855 -0
- regscale/integrations/commercial/sicura/commands.py +73 -0
- regscale/integrations/commercial/sicura/scanner.py +481 -0
- regscale/integrations/commercial/sicura/variables.py +16 -0
- regscale/integrations/commercial/snyk.py +90 -0
- regscale/integrations/commercial/sonarcloud.py +260 -0
- regscale/integrations/commercial/sqlserver.py +369 -0
- regscale/integrations/commercial/stig_mapper_integration/__init__.py +0 -0
- regscale/integrations/commercial/stig_mapper_integration/click_commands.py +38 -0
- regscale/integrations/commercial/stig_mapper_integration/mapping_engine.py +353 -0
- regscale/integrations/commercial/stigv2/__init__.py +0 -0
- regscale/integrations/commercial/stigv2/ckl_parser.py +349 -0
- regscale/integrations/commercial/stigv2/click_commands.py +95 -0
- regscale/integrations/commercial/stigv2/stig_integration.py +202 -0
- regscale/integrations/commercial/synqly/__init__.py +0 -0
- regscale/integrations/commercial/synqly/assets.py +46 -0
- regscale/integrations/commercial/synqly/ticketing.py +132 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +223 -0
- regscale/integrations/commercial/synqly_jira.py +840 -0
- regscale/integrations/commercial/tenablev2/__init__.py +0 -0
- regscale/integrations/commercial/tenablev2/authenticate.py +31 -0
- regscale/integrations/commercial/tenablev2/click.py +1584 -0
- regscale/integrations/commercial/tenablev2/scanner.py +504 -0
- regscale/integrations/commercial/tenablev2/stig_parsers.py +140 -0
- regscale/integrations/commercial/tenablev2/utils.py +78 -0
- regscale/integrations/commercial/tenablev2/variables.py +17 -0
- regscale/integrations/commercial/trivy.py +162 -0
- regscale/integrations/commercial/veracode.py +96 -0
- regscale/integrations/commercial/wizv2/WizDataMixin.py +97 -0
- regscale/integrations/commercial/wizv2/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/click.py +429 -0
- regscale/integrations/commercial/wizv2/constants.py +1001 -0
- regscale/integrations/commercial/wizv2/issue.py +361 -0
- regscale/integrations/commercial/wizv2/models.py +112 -0
- regscale/integrations/commercial/wizv2/parsers.py +339 -0
- regscale/integrations/commercial/wizv2/sbom.py +115 -0
- regscale/integrations/commercial/wizv2/scanner.py +416 -0
- regscale/integrations/commercial/wizv2/utils.py +796 -0
- regscale/integrations/commercial/wizv2/variables.py +39 -0
- regscale/integrations/commercial/wizv2/wiz_auth.py +159 -0
- regscale/integrations/commercial/xray.py +91 -0
- regscale/integrations/integration/__init__.py +2 -0
- regscale/integrations/integration/integration.py +26 -0
- regscale/integrations/integration/inventory.py +17 -0
- regscale/integrations/integration/issue.py +100 -0
- regscale/integrations/integration_override.py +149 -0
- regscale/integrations/public/__init__.py +103 -0
- regscale/integrations/public/cisa.py +641 -0
- regscale/integrations/public/criticality_updater.py +70 -0
- regscale/integrations/public/emass.py +411 -0
- regscale/integrations/public/emass_slcm_import.py +697 -0
- regscale/integrations/public/fedramp/__init__.py +0 -0
- regscale/integrations/public/fedramp/appendix_parser.py +548 -0
- regscale/integrations/public/fedramp/click.py +479 -0
- regscale/integrations/public/fedramp/components.py +714 -0
- regscale/integrations/public/fedramp/docx_parser.py +259 -0
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +1124 -0
- regscale/integrations/public/fedramp/fedramp_common.py +3181 -0
- regscale/integrations/public/fedramp/fedramp_docx.py +388 -0
- regscale/integrations/public/fedramp/fedramp_five.py +2343 -0
- regscale/integrations/public/fedramp/fedramp_traversal.py +138 -0
- regscale/integrations/public/fedramp/import_fedramp_r4_ssp.py +279 -0
- regscale/integrations/public/fedramp/import_workbook.py +495 -0
- regscale/integrations/public/fedramp/inventory_items.py +244 -0
- regscale/integrations/public/fedramp/mappings/__init__.py +0 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +7388 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r5_params.json +8636 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +9605 -0
- regscale/integrations/public/fedramp/mappings/system_roles.py +34 -0
- regscale/integrations/public/fedramp/mappings/user.py +175 -0
- regscale/integrations/public/fedramp/mappings/values.py +141 -0
- regscale/integrations/public/fedramp/markdown_parser.py +150 -0
- regscale/integrations/public/fedramp/metadata.py +689 -0
- regscale/integrations/public/fedramp/models/__init__.py +59 -0
- regscale/integrations/public/fedramp/models/leveraged_auth_new.py +168 -0
- regscale/integrations/public/fedramp/models/poam_importer.py +522 -0
- regscale/integrations/public/fedramp/parts_mapper.py +107 -0
- regscale/integrations/public/fedramp/poam/__init__.py +0 -0
- regscale/integrations/public/fedramp/poam/scanner.py +851 -0
- regscale/integrations/public/fedramp/properties.py +201 -0
- regscale/integrations/public/fedramp/reporting.py +84 -0
- regscale/integrations/public/fedramp/resources.py +496 -0
- regscale/integrations/public/fedramp/rosetta.py +110 -0
- regscale/integrations/public/fedramp/ssp_logger.py +87 -0
- regscale/integrations/public/fedramp/system_characteristics.py +922 -0
- regscale/integrations/public/fedramp/system_control_implementations.py +582 -0
- regscale/integrations/public/fedramp/system_implementation.py +190 -0
- regscale/integrations/public/fedramp/xml_utils.py +87 -0
- regscale/integrations/public/nist_catalog.py +275 -0
- regscale/integrations/public/oscal.py +1946 -0
- regscale/integrations/public/otx.py +169 -0
- regscale/integrations/scanner_integration.py +2692 -0
- regscale/integrations/variables.py +25 -0
- regscale/models/__init__.py +7 -0
- regscale/models/app_models/__init__.py +5 -0
- regscale/models/app_models/catalog_compare.py +213 -0
- regscale/models/app_models/click.py +252 -0
- regscale/models/app_models/datetime_encoder.py +21 -0
- regscale/models/app_models/import_validater.py +321 -0
- regscale/models/app_models/mapping.py +260 -0
- regscale/models/app_models/pipeline.py +37 -0
- regscale/models/click_models.py +413 -0
- regscale/models/config.py +154 -0
- regscale/models/email_style.css +67 -0
- regscale/models/hierarchy.py +8 -0
- regscale/models/inspect_models.py +79 -0
- regscale/models/integration_models/__init__.py +0 -0
- regscale/models/integration_models/amazon_models/__init__.py +0 -0
- regscale/models/integration_models/amazon_models/inspector.py +262 -0
- regscale/models/integration_models/amazon_models/inspector_scan.py +206 -0
- regscale/models/integration_models/aqua.py +247 -0
- regscale/models/integration_models/azure_alerts.py +255 -0
- regscale/models/integration_models/base64.py +23 -0
- regscale/models/integration_models/burp.py +433 -0
- regscale/models/integration_models/burp_models.py +128 -0
- regscale/models/integration_models/cisa_kev_data.json +19333 -0
- regscale/models/integration_models/defender_data.py +93 -0
- regscale/models/integration_models/defenderimport.py +143 -0
- regscale/models/integration_models/drf.py +443 -0
- regscale/models/integration_models/ecr_models/__init__.py +0 -0
- regscale/models/integration_models/ecr_models/data.py +69 -0
- regscale/models/integration_models/ecr_models/ecr.py +239 -0
- regscale/models/integration_models/flat_file_importer.py +1079 -0
- regscale/models/integration_models/grype_import.py +247 -0
- regscale/models/integration_models/ibm.py +126 -0
- regscale/models/integration_models/implementation_results.py +85 -0
- regscale/models/integration_models/nexpose.py +140 -0
- regscale/models/integration_models/prisma.py +202 -0
- regscale/models/integration_models/qualys.py +720 -0
- regscale/models/integration_models/qualys_scanner.py +160 -0
- regscale/models/integration_models/sbom/__init__.py +0 -0
- regscale/models/integration_models/sbom/cyclone_dx.py +139 -0
- regscale/models/integration_models/send_reminders.py +620 -0
- regscale/models/integration_models/snyk.py +155 -0
- regscale/models/integration_models/synqly_models/__init__.py +0 -0
- regscale/models/integration_models/synqly_models/capabilities.json +1 -0
- regscale/models/integration_models/synqly_models/connector_types.py +22 -0
- regscale/models/integration_models/synqly_models/connectors/__init__.py +7 -0
- regscale/models/integration_models/synqly_models/connectors/assets.py +97 -0
- regscale/models/integration_models/synqly_models/connectors/ticketing.py +583 -0
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +169 -0
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +331 -0
- regscale/models/integration_models/synqly_models/param.py +72 -0
- regscale/models/integration_models/synqly_models/synqly_model.py +733 -0
- regscale/models/integration_models/synqly_models/tenants.py +39 -0
- regscale/models/integration_models/tenable_models/__init__.py +0 -0
- regscale/models/integration_models/tenable_models/integration.py +187 -0
- regscale/models/integration_models/tenable_models/models.py +513 -0
- regscale/models/integration_models/trivy_import.py +231 -0
- regscale/models/integration_models/veracode.py +217 -0
- regscale/models/integration_models/xray.py +135 -0
- regscale/models/locking.py +100 -0
- regscale/models/platform.py +110 -0
- regscale/models/regscale_models/__init__.py +67 -0
- regscale/models/regscale_models/assessment.py +570 -0
- regscale/models/regscale_models/assessment_plan.py +52 -0
- regscale/models/regscale_models/asset.py +567 -0
- regscale/models/regscale_models/asset_mapping.py +190 -0
- regscale/models/regscale_models/case.py +42 -0
- regscale/models/regscale_models/catalog.py +261 -0
- regscale/models/regscale_models/cci.py +46 -0
- regscale/models/regscale_models/change.py +167 -0
- regscale/models/regscale_models/checklist.py +372 -0
- regscale/models/regscale_models/comment.py +49 -0
- regscale/models/regscale_models/compliance_settings.py +112 -0
- regscale/models/regscale_models/component.py +412 -0
- regscale/models/regscale_models/component_mapping.py +65 -0
- regscale/models/regscale_models/control.py +38 -0
- regscale/models/regscale_models/control_implementation.py +1128 -0
- regscale/models/regscale_models/control_objective.py +261 -0
- regscale/models/regscale_models/control_parameter.py +100 -0
- regscale/models/regscale_models/control_test.py +34 -0
- regscale/models/regscale_models/control_test_plan.py +75 -0
- regscale/models/regscale_models/control_test_result.py +52 -0
- regscale/models/regscale_models/custom_field.py +245 -0
- regscale/models/regscale_models/data.py +109 -0
- regscale/models/regscale_models/data_center.py +40 -0
- regscale/models/regscale_models/deviation.py +203 -0
- regscale/models/regscale_models/email.py +97 -0
- regscale/models/regscale_models/evidence.py +47 -0
- regscale/models/regscale_models/evidence_mapping.py +40 -0
- regscale/models/regscale_models/facility.py +59 -0
- regscale/models/regscale_models/file.py +382 -0
- regscale/models/regscale_models/filetag.py +37 -0
- regscale/models/regscale_models/form_field_value.py +94 -0
- regscale/models/regscale_models/group.py +169 -0
- regscale/models/regscale_models/implementation_objective.py +335 -0
- regscale/models/regscale_models/implementation_option.py +275 -0
- regscale/models/regscale_models/implementation_role.py +33 -0
- regscale/models/regscale_models/incident.py +177 -0
- regscale/models/regscale_models/interconnection.py +43 -0
- regscale/models/regscale_models/issue.py +1176 -0
- regscale/models/regscale_models/leveraged_authorization.py +125 -0
- regscale/models/regscale_models/line_of_inquiry.py +52 -0
- regscale/models/regscale_models/link.py +205 -0
- regscale/models/regscale_models/meta_data.py +64 -0
- regscale/models/regscale_models/mixins/__init__.py +0 -0
- regscale/models/regscale_models/mixins/parent_cache.py +124 -0
- regscale/models/regscale_models/module.py +224 -0
- regscale/models/regscale_models/modules.py +191 -0
- regscale/models/regscale_models/objective.py +14 -0
- regscale/models/regscale_models/parameter.py +87 -0
- regscale/models/regscale_models/ports_protocol.py +81 -0
- regscale/models/regscale_models/privacy.py +89 -0
- regscale/models/regscale_models/profile.py +50 -0
- regscale/models/regscale_models/profile_link.py +68 -0
- regscale/models/regscale_models/profile_mapping.py +124 -0
- regscale/models/regscale_models/project.py +63 -0
- regscale/models/regscale_models/property.py +278 -0
- regscale/models/regscale_models/question.py +85 -0
- regscale/models/regscale_models/questionnaire.py +87 -0
- regscale/models/regscale_models/questionnaire_instance.py +177 -0
- regscale/models/regscale_models/rbac.py +132 -0
- regscale/models/regscale_models/reference.py +86 -0
- regscale/models/regscale_models/regscale_model.py +1643 -0
- regscale/models/regscale_models/requirement.py +29 -0
- regscale/models/regscale_models/risk.py +274 -0
- regscale/models/regscale_models/sbom.py +54 -0
- regscale/models/regscale_models/scan_history.py +436 -0
- regscale/models/regscale_models/search.py +53 -0
- regscale/models/regscale_models/security_control.py +132 -0
- regscale/models/regscale_models/security_plan.py +204 -0
- regscale/models/regscale_models/software_inventory.py +159 -0
- regscale/models/regscale_models/stake_holder.py +64 -0
- regscale/models/regscale_models/stig.py +647 -0
- regscale/models/regscale_models/supply_chain.py +152 -0
- regscale/models/regscale_models/system_role.py +188 -0
- regscale/models/regscale_models/system_role_external_assignment.py +40 -0
- regscale/models/regscale_models/tag.py +37 -0
- regscale/models/regscale_models/tag_mapping.py +19 -0
- regscale/models/regscale_models/task.py +133 -0
- regscale/models/regscale_models/threat.py +196 -0
- regscale/models/regscale_models/user.py +175 -0
- regscale/models/regscale_models/user_group.py +55 -0
- regscale/models/regscale_models/vulnerability.py +242 -0
- regscale/models/regscale_models/vulnerability_mapping.py +162 -0
- regscale/models/regscale_models/workflow.py +55 -0
- regscale/models/regscale_models/workflow_action.py +34 -0
- regscale/models/regscale_models/workflow_instance.py +269 -0
- regscale/models/regscale_models/workflow_instance_step.py +114 -0
- regscale/models/regscale_models/workflow_template.py +58 -0
- regscale/models/regscale_models/workflow_template_step.py +45 -0
- regscale/regscale.py +815 -0
- regscale/utils/__init__.py +7 -0
- regscale/utils/b64conversion.py +14 -0
- regscale/utils/click_utils.py +118 -0
- regscale/utils/decorators.py +48 -0
- regscale/utils/dict_utils.py +59 -0
- regscale/utils/files.py +79 -0
- regscale/utils/fxns.py +30 -0
- regscale/utils/graphql_client.py +113 -0
- regscale/utils/lists.py +16 -0
- regscale/utils/numbers.py +12 -0
- regscale/utils/shell.py +148 -0
- regscale/utils/string.py +121 -0
- regscale/utils/synqly_utils.py +165 -0
- regscale/utils/threading/__init__.py +8 -0
- regscale/utils/threading/threadhandler.py +131 -0
- regscale/utils/threading/threadsafe_counter.py +47 -0
- regscale/utils/threading/threadsafe_dict.py +242 -0
- regscale/utils/threading/threadsafe_list.py +83 -0
- regscale/utils/version.py +104 -0
- regscale/validation/__init__.py +0 -0
- regscale/validation/address.py +37 -0
- regscale/validation/record.py +48 -0
- regscale/visualization/__init__.py +5 -0
- regscale/visualization/click.py +34 -0
- regscale_cli-6.16.0.0.dist-info/LICENSE +21 -0
- regscale_cli-6.16.0.0.dist-info/METADATA +659 -0
- regscale_cli-6.16.0.0.dist-info/RECORD +481 -0
- regscale_cli-6.16.0.0.dist-info/WHEEL +5 -0
- regscale_cli-6.16.0.0.dist-info/entry_points.txt +6 -0
- regscale_cli-6.16.0.0.dist-info/top_level.txt +2 -0
- tests/fixtures/__init__.py +2 -0
- tests/fixtures/api.py +87 -0
- tests/fixtures/models.py +91 -0
- tests/fixtures/test_fixture.py +144 -0
- tests/mocks/__init__.py +0 -0
- tests/mocks/objects.py +3 -0
- tests/mocks/response.py +32 -0
- tests/mocks/xml.py +13 -0
- tests/regscale/__init__.py +0 -0
- tests/regscale/core/__init__.py +0 -0
- tests/regscale/core/test_api.py +232 -0
- tests/regscale/core/test_app.py +406 -0
- tests/regscale/core/test_login.py +37 -0
- tests/regscale/core/test_logz.py +66 -0
- tests/regscale/core/test_sbom_generator.py +87 -0
- tests/regscale/core/test_validation_utils.py +163 -0
- tests/regscale/core/test_version.py +78 -0
- tests/regscale/models/__init__.py +0 -0
- tests/regscale/models/test_asset.py +71 -0
- tests/regscale/models/test_config.py +26 -0
- tests/regscale/models/test_control_implementation.py +27 -0
- tests/regscale/models/test_import.py +97 -0
- tests/regscale/models/test_issue.py +36 -0
- tests/regscale/models/test_mapping.py +52 -0
- tests/regscale/models/test_platform.py +31 -0
- tests/regscale/models/test_regscale_model.py +346 -0
- tests/regscale/models/test_report.py +32 -0
- tests/regscale/models/test_tenable_integrations.py +118 -0
- tests/regscale/models/test_user_model.py +121 -0
- tests/regscale/test_about.py +19 -0
- tests/regscale/test_authorization.py +65 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""Data model that will be used to validate the input data and that it has the required headers before proceeding."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from regscale.models import Mapping
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from regscale.exceptions.validation_exception import ValidationException
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("regscale")
|
|
16
|
+
JSON = ".json"
|
|
17
|
+
XLSX = ".xlsx"
|
|
18
|
+
XLSM = ".xlsm"
|
|
19
|
+
CSV = ".csv"
|
|
20
|
+
XML = ".xml"
|
|
21
|
+
NESSUS = ".nessus"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ImportValidater:
|
|
25
|
+
"""
|
|
26
|
+
Data importer that validates the input data
|
|
27
|
+
|
|
28
|
+
:param list required_headers: List of required headers (columns)
|
|
29
|
+
:param Union[str, Path] file_path: Path to the file to import
|
|
30
|
+
:raises ValidationException: If the file type is not supported
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
_supported_types = [CSV, XLSX, XLSM, JSON, XML, NESSUS]
|
|
34
|
+
xml_tag = "record"
|
|
35
|
+
keys: Optional[list] = []
|
|
36
|
+
key: Optional[str] = ""
|
|
37
|
+
required_headers: list
|
|
38
|
+
parsed_headers: list
|
|
39
|
+
data: Union["pd.DataFrame", list]
|
|
40
|
+
mapping: "Mapping"
|
|
41
|
+
worksheet_name: Optional[str] = None
|
|
42
|
+
skip_rows: Optional[int] = None
|
|
43
|
+
file_path: Path
|
|
44
|
+
file_type: str
|
|
45
|
+
mapping_file_path: Path
|
|
46
|
+
disable_mapping: bool
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
required_headers: list,
|
|
51
|
+
file_path: Union[str, Path],
|
|
52
|
+
mapping_file_path: Union[str, Path],
|
|
53
|
+
disable_mapping: bool = False,
|
|
54
|
+
xml_tag: Optional[str] = None,
|
|
55
|
+
key: Optional[str] = None,
|
|
56
|
+
keys: Optional[list] = None,
|
|
57
|
+
worksheet_name: Optional[str] = None,
|
|
58
|
+
skip_rows: Optional[int] = None,
|
|
59
|
+
prompt: bool = True,
|
|
60
|
+
ignore_unnamed: bool = False,
|
|
61
|
+
):
|
|
62
|
+
self.ignore_unnamed = ignore_unnamed
|
|
63
|
+
self.prompt = prompt
|
|
64
|
+
self.required_headers = required_headers
|
|
65
|
+
file_path = self._convert_str_to_path(file_path)
|
|
66
|
+
self.mapping_file_path = self._convert_str_to_path(mapping_file_path)
|
|
67
|
+
self.file_path = file_path
|
|
68
|
+
self.file_type = file_path.suffix
|
|
69
|
+
if not self.mapping_file_path.suffix:
|
|
70
|
+
self.mapping_file_path = Path(self.mapping_file_path / f"{self.file_type[1:]}_mapping.json")
|
|
71
|
+
self.disable_mapping = disable_mapping
|
|
72
|
+
self.xml_tag = xml_tag
|
|
73
|
+
self.key = key
|
|
74
|
+
self.keys = keys
|
|
75
|
+
self.worksheet_name = worksheet_name
|
|
76
|
+
self.skip_rows = skip_rows
|
|
77
|
+
if self.file_type not in self._supported_types:
|
|
78
|
+
raise ValidationException(
|
|
79
|
+
f"Unsupported file type: {self.file_type}, supported types are: {', '.join(self._supported_types)}",
|
|
80
|
+
)
|
|
81
|
+
self._parse_headers()
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _convert_str_to_path(file_path: Union[str, Path]) -> Path:
|
|
85
|
+
"""
|
|
86
|
+
Converts a string to a Path object
|
|
87
|
+
|
|
88
|
+
:param Union[str, Path] file_path: File path as a string
|
|
89
|
+
:returns: Path object
|
|
90
|
+
:rtype: Path
|
|
91
|
+
"""
|
|
92
|
+
if isinstance(file_path, Path):
|
|
93
|
+
return file_path
|
|
94
|
+
return Path(file_path)
|
|
95
|
+
|
|
96
|
+
def import_data(self) -> Union["pd.DataFrame", list, dict]:
|
|
97
|
+
"""
|
|
98
|
+
Imports the data from the file and returns it
|
|
99
|
+
|
|
100
|
+
:returns: Imported data
|
|
101
|
+
:rtype: Union[pd.DataFrame, list, dict]
|
|
102
|
+
"""
|
|
103
|
+
if self.file_type == CSV:
|
|
104
|
+
return self.import_csv(self.file_path)
|
|
105
|
+
elif self.file_type == XLSX or self.file_type == XLSM:
|
|
106
|
+
return self.import_xlsx(self.file_path)
|
|
107
|
+
elif self.file_type == JSON:
|
|
108
|
+
return self.import_json(self.file_path)
|
|
109
|
+
elif self.file_type in (XML, NESSUS):
|
|
110
|
+
return self.import_xml(self.file_path, self.xml_tag)
|
|
111
|
+
|
|
112
|
+
def _parse_headers(self):
|
|
113
|
+
"""
|
|
114
|
+
Parses the headers from the file based on the file type and returns them
|
|
115
|
+
|
|
116
|
+
:returns: List of headers
|
|
117
|
+
:rtype: list
|
|
118
|
+
"""
|
|
119
|
+
self.data = self.import_data()
|
|
120
|
+
if self.file_type in [CSV, XLSX, XLSM]:
|
|
121
|
+
self.parsed_headers = self.data.columns # type: ignore
|
|
122
|
+
elif self.file_type == XML:
|
|
123
|
+
self.parsed_headers = list(self.data.keys())
|
|
124
|
+
elif self.file_type == JSON and not self.parsed_headers:
|
|
125
|
+
raise ValidationException(f"Unable to parse headers from JSON file: {self.file_path}")
|
|
126
|
+
|
|
127
|
+
def validate_headers(self, headers: Union[list, "pd.Index"]) -> None:
|
|
128
|
+
"""
|
|
129
|
+
Validates that all required headers are present
|
|
130
|
+
|
|
131
|
+
:param Union[list, pd.Index] headers: List of headers from the file
|
|
132
|
+
"""
|
|
133
|
+
import re
|
|
134
|
+
|
|
135
|
+
from regscale.models import Mapping
|
|
136
|
+
|
|
137
|
+
if not self.ignore_unnamed and any(re.search(r"unnamed", header, re.IGNORECASE) for header in headers): # type: ignore
|
|
138
|
+
raise ValidationException(
|
|
139
|
+
f"Unable to parse headers from the file. Please ensure the headers are named in {self.file_path}"
|
|
140
|
+
f"\n{', '.join(headers)}"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if not self.prompt:
|
|
144
|
+
# Let's not prompt the user to find the header, and just raise a validation exception
|
|
145
|
+
missing_headers = [header for header in self.required_headers if header not in headers]
|
|
146
|
+
extra_headers = [header for header in headers if header not in self.required_headers]
|
|
147
|
+
if missing_headers:
|
|
148
|
+
raise ValidationException(
|
|
149
|
+
f"{', '.join([f'`{header}`' for header in missing_headers])} header(s) not found in {self.file_path}"
|
|
150
|
+
)
|
|
151
|
+
if extra_headers:
|
|
152
|
+
logger.warning("Extra headers found in the file: %s", ", ".join(extra_headers))
|
|
153
|
+
|
|
154
|
+
if self.disable_mapping:
|
|
155
|
+
logger.debug("Mapping is disabled, using headers as is.")
|
|
156
|
+
self.mapping = Mapping(
|
|
157
|
+
mapping={header: header for header in headers},
|
|
158
|
+
expected_field_names=self.required_headers,
|
|
159
|
+
file_path_for_prompt=self.file_path,
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
logger.debug(f"Mapping is enabled, validating headers for {self.file_path}")
|
|
163
|
+
self.mapping = Mapping.from_file(
|
|
164
|
+
file_path=self.mapping_file_path,
|
|
165
|
+
expected_field_names=self.required_headers,
|
|
166
|
+
mapping={"mapping": {header: header for header in headers}},
|
|
167
|
+
parsed_headers=[header for header in headers], # this converts the pd.Index to a list
|
|
168
|
+
file_path_for_prompt=self.file_path,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def import_csv(self, file_path: Union[str, Path]) -> "pd.DataFrame":
|
|
172
|
+
"""
|
|
173
|
+
Imports a CSV file and validates headers
|
|
174
|
+
|
|
175
|
+
:param file_path: Path to the CSV file
|
|
176
|
+
:returns: DataFrame with the imported data
|
|
177
|
+
:rtype: pd.DataFrame
|
|
178
|
+
"""
|
|
179
|
+
import pandas
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
if self.skip_rows:
|
|
183
|
+
df = pandas.read_csv(file_path, skiprows=self.skip_rows - 1, on_bad_lines="warn")
|
|
184
|
+
else:
|
|
185
|
+
df = pandas.read_csv(file_path, on_bad_lines="warn")
|
|
186
|
+
except pandas.errors.ParserError:
|
|
187
|
+
raise ValidationException(f"Unable to parse the {CSV} file: {file_path}")
|
|
188
|
+
self.validate_headers(df.columns)
|
|
189
|
+
df = df.fillna("")
|
|
190
|
+
return df
|
|
191
|
+
|
|
192
|
+
def import_xlsx(self, file_path: Union[str, Path]) -> "pd.DataFrame":
|
|
193
|
+
"""
|
|
194
|
+
Imports an XLSX file and validates headers
|
|
195
|
+
|
|
196
|
+
:param Union[str, Path] file_path: Path to the XLSX file
|
|
197
|
+
:returns: DataFrame with the imported data
|
|
198
|
+
:rtype: pd.DataFrame
|
|
199
|
+
"""
|
|
200
|
+
import pandas
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
if self.skip_rows and self.worksheet_name:
|
|
204
|
+
df = pandas.read_excel(file_path, sheet_name=self.worksheet_name, skiprows=self.skip_rows - 1)
|
|
205
|
+
elif self.worksheet_name:
|
|
206
|
+
df = pandas.read_excel(file_path, sheet_name=self.worksheet_name)
|
|
207
|
+
elif self.skip_rows:
|
|
208
|
+
df = pandas.read_excel(file_path, skiprows=self.skip_rows)
|
|
209
|
+
else:
|
|
210
|
+
df = pandas.read_excel(file_path)
|
|
211
|
+
except Exception as e:
|
|
212
|
+
raise ValidationException(f"Unable to parse the {XLSX} file: {file_path}\nDetails: {e}")
|
|
213
|
+
self.validate_headers(df.columns)
|
|
214
|
+
df = df.fillna("")
|
|
215
|
+
return df
|
|
216
|
+
|
|
217
|
+
def _handle_keys(self, data: dict) -> list:
|
|
218
|
+
"""
|
|
219
|
+
Handles the keys for JSON data
|
|
220
|
+
|
|
221
|
+
:param dict data: JSON data
|
|
222
|
+
|
|
223
|
+
:returns: List of keys
|
|
224
|
+
:rtype: list
|
|
225
|
+
"""
|
|
226
|
+
if self.keys:
|
|
227
|
+
value = data
|
|
228
|
+
# iterate each key and see if it is in the data
|
|
229
|
+
for key in self.keys:
|
|
230
|
+
value = value.get(key, {})
|
|
231
|
+
if isinstance(value, dict):
|
|
232
|
+
return list(value.keys())
|
|
233
|
+
elif isinstance(value, list):
|
|
234
|
+
if value:
|
|
235
|
+
return list(value[0].keys())
|
|
236
|
+
else:
|
|
237
|
+
raise ValidationException(
|
|
238
|
+
f"JSON file must contain a key '{self.keys[-1]}' with a list of dictionaries"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return data.get(self.key) or data.get(self.keys[0])
|
|
242
|
+
|
|
243
|
+
def import_json(self, file_path: Union[str, Path]) -> list:
|
|
244
|
+
"""
|
|
245
|
+
Imports a JSON file and validates keys (treated as headers)
|
|
246
|
+
|
|
247
|
+
:param file_path: Path to the JSON file
|
|
248
|
+
:raises ValidationException: If the JSON file is empty or not a list of dictionaries
|
|
249
|
+
:returns: List of dictionaries with the imported data
|
|
250
|
+
:rtype: list
|
|
251
|
+
"""
|
|
252
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
253
|
+
data = json.load(f)
|
|
254
|
+
|
|
255
|
+
if isinstance(data, list) and not self.key and not self.keys and data:
|
|
256
|
+
# Assuming JSON is a list of dictionaries
|
|
257
|
+
self.validate_headers(data[0].keys())
|
|
258
|
+
self.parsed_headers = list(data[0].keys())
|
|
259
|
+
elif isinstance(data, list) and self.keys and data:
|
|
260
|
+
# Assuming JSON is a list of dictionaries
|
|
261
|
+
self.validate_headers(self._handle_keys(data[0]))
|
|
262
|
+
self.parsed_headers = list(self._handle_keys(data[0]))
|
|
263
|
+
elif isinstance(data, dict) and (self.key or self.keys):
|
|
264
|
+
if self.keys:
|
|
265
|
+
self.validate_headers(self._handle_keys(data))
|
|
266
|
+
self.parsed_headers = self._handle_keys(data)
|
|
267
|
+
elif findings := data.get(self.key):
|
|
268
|
+
self.validate_headers(findings[0].keys())
|
|
269
|
+
self.parsed_headers = list(findings[0].keys())
|
|
270
|
+
elif isinstance(data, dict):
|
|
271
|
+
self.validate_headers(list(data.keys()))
|
|
272
|
+
self.parsed_headers = list(data.keys())
|
|
273
|
+
else:
|
|
274
|
+
raise ValidationException(f"Unable to parse headers from JSON file: {file_path}")
|
|
275
|
+
|
|
276
|
+
return data
|
|
277
|
+
|
|
278
|
+
def _remove_at_prefix(self, xml_data: Union[list, dict]) -> Union[list, dict]:
|
|
279
|
+
"""
|
|
280
|
+
Recursively remove the '@' prefix from keys in a dictionary
|
|
281
|
+
|
|
282
|
+
:param Union[list, dict] xml_data: Parsed XML data, either a dictionary or a list of dictionaries
|
|
283
|
+
:returns: Dictionary with '@' prefix removed or list of dictionaries with '@' prefix removed
|
|
284
|
+
:rtype: Union[list, dict]
|
|
285
|
+
"""
|
|
286
|
+
if isinstance(xml_data, dict):
|
|
287
|
+
return {k.lstrip("@"): self._remove_at_prefix(v) for k, v in xml_data.items()}
|
|
288
|
+
elif isinstance(xml_data, list):
|
|
289
|
+
return [self._remove_at_prefix(i) for i in xml_data]
|
|
290
|
+
else:
|
|
291
|
+
return xml_data
|
|
292
|
+
|
|
293
|
+
def import_xml(self, file_path: Union[str, Path], record_tag: Optional[str] = None) -> dict:
|
|
294
|
+
"""
|
|
295
|
+
Imports an XML file and validates keys (treated as headers)
|
|
296
|
+
|
|
297
|
+
:param Union[str, Path] file_path: Path to the XML file
|
|
298
|
+
:param str record_tag: The XML tag that represents a record, defaults to "record"
|
|
299
|
+
:raises ValidationException: If the XML file contains no records
|
|
300
|
+
:returns: Dictionary with the imported data
|
|
301
|
+
:rtype: dict
|
|
302
|
+
"""
|
|
303
|
+
import xmltodict
|
|
304
|
+
|
|
305
|
+
if isinstance(file_path, str):
|
|
306
|
+
file_path = Path(file_path)
|
|
307
|
+
with open(file_path, "r", encoding="utf-8") as file:
|
|
308
|
+
xml_content = file.read()
|
|
309
|
+
try:
|
|
310
|
+
if dict_content := xmltodict.parse(xml_content):
|
|
311
|
+
if record_tag:
|
|
312
|
+
dict_content = dict_content.get(record_tag)
|
|
313
|
+
dict_content = self._remove_at_prefix(dict_content)
|
|
314
|
+
self.validate_headers(list(dict_content.keys()))
|
|
315
|
+
return dict_content
|
|
316
|
+
else:
|
|
317
|
+
raise ValidationException("XML file contains no records.")
|
|
318
|
+
except xmltodict.expat.ExpatError as e:
|
|
319
|
+
raise ValidationException(
|
|
320
|
+
f"Error parsing {self.file_type.strip('.')} file: {file_path}.\nDetails: {e.args[0]}"
|
|
321
|
+
) from e
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Pydantic class for custom container scan mappings"""
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from json import JSONDecodeError
|
|
7
|
+
from logging import Logger
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, field_validator
|
|
12
|
+
|
|
13
|
+
from regscale.core.app import create_logger
|
|
14
|
+
from regscale.core.app.utils.app_utils import check_file_path, error_and_exit, save_data_to
|
|
15
|
+
from regscale.exceptions import ValidationException
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Mapping(BaseModel):
|
|
19
|
+
"""
|
|
20
|
+
Pydantic class for custom container scan mappings
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
model_config = ConfigDict(populate_by_name=True, json_encoders={bytes: lambda v: v.decode()})
|
|
24
|
+
|
|
25
|
+
mapping: Dict[str, str]
|
|
26
|
+
# expected field names for validation
|
|
27
|
+
expected_field_names: List[str] = []
|
|
28
|
+
_logger: Optional[Logger] = logging.getLogger("regscale")
|
|
29
|
+
file_path_for_prompt: Optional[Path] = None
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def _prompt_for_field(
|
|
33
|
+
cls,
|
|
34
|
+
field: str,
|
|
35
|
+
mapping: Union[dict, "Mapping"],
|
|
36
|
+
parsed_headers: Optional[List[str]] = None,
|
|
37
|
+
file_path_for_prompt: Optional[Path] = None,
|
|
38
|
+
) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Prompt for a field value
|
|
41
|
+
|
|
42
|
+
:param str field: Field to prompt for
|
|
43
|
+
:param Union[dict, "Mapping"] mapping: Mapping object or dictionary of the mapping object
|
|
44
|
+
:param Optional[List[str]] parsed_headers: Parsed headers from file, defaults to None
|
|
45
|
+
:return: Field value
|
|
46
|
+
:rtype: str
|
|
47
|
+
"""
|
|
48
|
+
info_msg = f"Field '{field}' not found in mapping, please enter the mapping for this field, enter 'exit' to exit, or enter 'skip' to skip"
|
|
49
|
+
if file_path_for_prompt:
|
|
50
|
+
info_msg += f"\nFile path for headers: {file_path_for_prompt.name}"
|
|
51
|
+
if parsed_headers:
|
|
52
|
+
info_msg += f"\nParsed headers: {parsed_headers}"
|
|
53
|
+
cls._logger.default.info(info_msg)
|
|
54
|
+
custom_mapping = input(f"Enter the mapping for field '{field}':")
|
|
55
|
+
cls._handle_user_input(custom_mapping, field, mapping, parsed_headers, file_path_for_prompt)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def _handle_user_input(
|
|
59
|
+
cls,
|
|
60
|
+
custom_mapping: str,
|
|
61
|
+
field: str,
|
|
62
|
+
mapping: Union[dict, "Mapping"],
|
|
63
|
+
parsed_headers: Optional[List[str]],
|
|
64
|
+
file_path_for_prompt: Optional[Path],
|
|
65
|
+
) -> None:
|
|
66
|
+
if custom_mapping.lower() in ["exit", "skip"]:
|
|
67
|
+
cls._handle_exit_or_skip(custom_mapping, file_path_for_prompt)
|
|
68
|
+
else:
|
|
69
|
+
cls._confirm_and_update_mapping(custom_mapping, field, mapping, parsed_headers)
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def _handle_exit_or_skip(cls, custom_mapping: str, file_path_for_prompt: Optional[Path]) -> None:
|
|
73
|
+
if custom_mapping.lower() == "exit":
|
|
74
|
+
error_and_exit("Exiting...")
|
|
75
|
+
elif custom_mapping.lower() == "skip":
|
|
76
|
+
raise ValidationException(f"Skipping file {file_path_for_prompt.name if file_path_for_prompt else ''}...")
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def _confirm_and_update_mapping(
|
|
80
|
+
cls, custom_mapping: str, field: str, mapping: Union[dict, "Mapping"], parsed_headers: Optional[List[str]]
|
|
81
|
+
) -> None:
|
|
82
|
+
confirm = input(f"Confirm the mapping for field '{field}' is '{custom_mapping}' (y/n)")
|
|
83
|
+
if confirm.lower() == "y":
|
|
84
|
+
cls._validate_and_update_mapping(custom_mapping, field, mapping, parsed_headers)
|
|
85
|
+
elif confirm.lower() in ["exit", "skip"]:
|
|
86
|
+
cls._handle_exit_or_skip(confirm, None)
|
|
87
|
+
else:
|
|
88
|
+
cls._prompt_for_field(field=field, mapping=mapping, parsed_headers=parsed_headers)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def _validate_and_update_mapping(
|
|
92
|
+
cls, custom_mapping: str, field: str, mapping: Union[dict, "Mapping"], parsed_headers: Optional[List[str]]
|
|
93
|
+
) -> None:
|
|
94
|
+
if parsed_headers and custom_mapping not in parsed_headers:
|
|
95
|
+
cls._logger.default.warning(
|
|
96
|
+
f"Mapping {custom_mapping} is not found in the headers. "
|
|
97
|
+
f"Please select one of the following headers: {parsed_headers}."
|
|
98
|
+
)
|
|
99
|
+
cls._prompt_for_field(field=field, mapping=mapping, parsed_headers=parsed_headers)
|
|
100
|
+
elif custom_mapping not in mapping.values() and not parsed_headers:
|
|
101
|
+
cls._logger.default.warning(
|
|
102
|
+
f"Mapping {custom_mapping} is not found in the headers. "
|
|
103
|
+
f"Please select one of the following headers: {list(mapping.values())}."
|
|
104
|
+
)
|
|
105
|
+
cls._prompt_for_field(field=field, mapping=mapping, parsed_headers=parsed_headers)
|
|
106
|
+
else:
|
|
107
|
+
if isinstance(mapping, dict):
|
|
108
|
+
mapping[field] = custom_mapping
|
|
109
|
+
else:
|
|
110
|
+
mapping.mapping[field] = custom_mapping
|
|
111
|
+
|
|
112
|
+
@field_validator("expected_field_names")
|
|
113
|
+
def validate_mapping(cls: Type["Mapping"], expected_field_names: List[str], values: Dict[str, Any]) -> List[str]:
|
|
114
|
+
"""
|
|
115
|
+
Validate the expected field names
|
|
116
|
+
|
|
117
|
+
:param List[str] expected_field_names: Expected field names
|
|
118
|
+
:param Dict[str, Any] values: Values
|
|
119
|
+
:return: Expected field names
|
|
120
|
+
"""
|
|
121
|
+
mapping = values.data.get("mapping")
|
|
122
|
+
if mapping is not None and expected_field_names is not None:
|
|
123
|
+
if missing_fields := [field for field in expected_field_names if field not in mapping]:
|
|
124
|
+
for field in missing_fields:
|
|
125
|
+
cls._prompt_for_field(field, mapping, cls.file_path_for_prompt)
|
|
126
|
+
return expected_field_names
|
|
127
|
+
|
|
128
|
+
@field_validator("expected_field_names")
|
|
129
|
+
def validate_expected_field_names(cls: Type["Mapping"], expected_field_names: Any) -> List[str]:
|
|
130
|
+
"""
|
|
131
|
+
Validate the expected field names and types
|
|
132
|
+
|
|
133
|
+
:param Any expected_field_names: Expected field names
|
|
134
|
+
:raises ValidationError: If expected_field_names is not a list or if any element in the list is not a string
|
|
135
|
+
:rtype: List[str]
|
|
136
|
+
:return: Expected field names
|
|
137
|
+
"""
|
|
138
|
+
if not isinstance(expected_field_names, list):
|
|
139
|
+
raise ValidationException("expected_field_names must be a list")
|
|
140
|
+
if not all(isinstance(field_name, str) for field_name in expected_field_names):
|
|
141
|
+
raise ValidationException("All elements in expected_field_names must be strings")
|
|
142
|
+
return expected_field_names
|
|
143
|
+
|
|
144
|
+
# Add a from file method to load the mapping from a JSON file
|
|
145
|
+
@classmethod
|
|
146
|
+
def from_file(cls, file_path: Path, expected_field_names: List[str], **kwargs) -> "Mapping":
|
|
147
|
+
"""
|
|
148
|
+
Load the mapping from a JSON file
|
|
149
|
+
|
|
150
|
+
:param Path file_path: Path to the JSON file
|
|
151
|
+
:param List[str] expected_field_names: Expected field names
|
|
152
|
+
:return: Validated Mapping ob
|
|
153
|
+
:rtype: Mapping
|
|
154
|
+
"""
|
|
155
|
+
file_path_for_prompt = kwargs.get("file_path_for_prompt")
|
|
156
|
+
if not file_path.exists() and kwargs.get("mapping"):
|
|
157
|
+
check_file_path(file_path.parent)
|
|
158
|
+
dat = kwargs.get("mapping")
|
|
159
|
+
else:
|
|
160
|
+
with open(file_path, "r") as file:
|
|
161
|
+
try:
|
|
162
|
+
dat = json.load(file)
|
|
163
|
+
# if mapping is not found in the JSON file, check the kwargs for the provided mapping and use that
|
|
164
|
+
if not dat.get("mapping"):
|
|
165
|
+
dat["mapping"] = kwargs.get("mapping")
|
|
166
|
+
if not dat.get("mapping"):
|
|
167
|
+
error_and_exit("Mapping not found in JSON file")
|
|
168
|
+
except JSONDecodeError as jex:
|
|
169
|
+
cls._logger.default.debug(jex)
|
|
170
|
+
error_and_exit("JSON file is badly formatted, please check the file")
|
|
171
|
+
except (ValueError, SyntaxError) as exc:
|
|
172
|
+
error_and_exit(f"Error parsing JSON file: {exc}")
|
|
173
|
+
if parsed_headers := kwargs.get("parsed_headers"):
|
|
174
|
+
cls._verify_parsed_headers(parsed_headers, expected_field_names, dat, file_path_for_prompt)
|
|
175
|
+
mapping = cls(
|
|
176
|
+
mapping=dat["mapping"],
|
|
177
|
+
expected_field_names=expected_field_names,
|
|
178
|
+
file_path_for_prompt=file_path_for_prompt,
|
|
179
|
+
)
|
|
180
|
+
if not mapping:
|
|
181
|
+
error_and_exit("Error loading mapping from file. Exiting...")
|
|
182
|
+
mapping.save_mapping(file_path)
|
|
183
|
+
return mapping
|
|
184
|
+
|
|
185
|
+
@classmethod
|
|
186
|
+
def _verify_parsed_headers(
|
|
187
|
+
cls, parsed_headers: List[str], expected_field_names: List[str], dat: dict, file_path_for_prompt: Optional[Path]
|
|
188
|
+
) -> None:
|
|
189
|
+
"""
|
|
190
|
+
Verify the parsed headers and prompt for missing headers
|
|
191
|
+
|
|
192
|
+
:param List[str] parsed_headers: Parsed headers
|
|
193
|
+
:param List[str] expected_field_names: Expected field names
|
|
194
|
+
:param Dict[str, Any] dat: Data dictionary
|
|
195
|
+
:param Optional[Path] file_path_for_prompt: File path for prompt
|
|
196
|
+
:rtype: None
|
|
197
|
+
"""
|
|
198
|
+
if missing_headers := [
|
|
199
|
+
header
|
|
200
|
+
for header in expected_field_names
|
|
201
|
+
if header not in parsed_headers and dat["mapping"].get(header) not in parsed_headers
|
|
202
|
+
]:
|
|
203
|
+
for header in missing_headers:
|
|
204
|
+
cls._logger.default.info(
|
|
205
|
+
f"Header '{header}' not found in parsed headers in {file_path_for_prompt.name if file_path_for_prompt else 'file'} Please enter the mapping."
|
|
206
|
+
)
|
|
207
|
+
cls._prompt_for_field(field=header, mapping=dat["mapping"], parsed_headers=parsed_headers)
|
|
208
|
+
|
|
209
|
+
def save_mapping(self, file_path: Path) -> None:
|
|
210
|
+
"""
|
|
211
|
+
Save the mapping to a JSON file
|
|
212
|
+
|
|
213
|
+
:param Path file_path: Path to save the mapping JSON file
|
|
214
|
+
:rtype: None
|
|
215
|
+
"""
|
|
216
|
+
check_file_path(file_path.parent)
|
|
217
|
+
save_data_to(file_path, {"mapping": self.mapping})
|
|
218
|
+
|
|
219
|
+
def get_header(self, key: str) -> str:
|
|
220
|
+
"""
|
|
221
|
+
Get the header for a key
|
|
222
|
+
|
|
223
|
+
:param str key: Key to get the header for
|
|
224
|
+
:return: Header for the key
|
|
225
|
+
:rtype: str
|
|
226
|
+
"""
|
|
227
|
+
return self.mapping.get(key)
|
|
228
|
+
|
|
229
|
+
def get_value(self, dat: Optional[dict], key: str, default_val: Optional[Any] = "", warnings: bool = True) -> Any:
|
|
230
|
+
"""
|
|
231
|
+
Get the value from a dictionary by mapped key
|
|
232
|
+
|
|
233
|
+
:param Optional[dict] dat: Data dictionary, defaults to None
|
|
234
|
+
:param str key: Key to get the value for
|
|
235
|
+
:param Optional[Any] default_val: Default value to return, defaults to empty string
|
|
236
|
+
:param bool warnings: Whether to log warnings, defaults to False
|
|
237
|
+
:return: Value for the key
|
|
238
|
+
:rtype: Any
|
|
239
|
+
"""
|
|
240
|
+
# check mapping
|
|
241
|
+
if key == "None" or key is None:
|
|
242
|
+
return default_val
|
|
243
|
+
mapped_key = self.mapping.get(key)
|
|
244
|
+
if not mapped_key and warnings:
|
|
245
|
+
self._logger.warning(f"Value for key '{key}' not found in mapping.")
|
|
246
|
+
if dat and mapped_key:
|
|
247
|
+
val = dat.get(mapped_key)
|
|
248
|
+
if isinstance(val, str):
|
|
249
|
+
return val.strip()
|
|
250
|
+
return val or default_val
|
|
251
|
+
return default_val
|
|
252
|
+
|
|
253
|
+
def to_header(self) -> list[str]:
|
|
254
|
+
"""
|
|
255
|
+
Convert the mapping to a header
|
|
256
|
+
:return: Mapping as a header
|
|
257
|
+
:rtype: list[str]
|
|
258
|
+
"""
|
|
259
|
+
# convert mapping to a list of strings
|
|
260
|
+
return [f"{value}" for key, value in self.mapping.items()]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Dataclass for a user's pipeline"""
|
|
4
|
+
|
|
5
|
+
# standard python imports
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Pipeline:
|
|
12
|
+
"""Pipeline Model"""
|
|
13
|
+
|
|
14
|
+
email: str # Required
|
|
15
|
+
fullName: str = None
|
|
16
|
+
pipelines: Any = None
|
|
17
|
+
totalTasks: int = None
|
|
18
|
+
analyzed: bool = False
|
|
19
|
+
emailed: bool = False
|
|
20
|
+
|
|
21
|
+
def __getitem__(self, key: Any) -> Any:
|
|
22
|
+
"""
|
|
23
|
+
Get attribute from Pipeline
|
|
24
|
+
:param Any key:
|
|
25
|
+
:return: value of provided key
|
|
26
|
+
:rtype: Any
|
|
27
|
+
"""
|
|
28
|
+
return getattr(self, key)
|
|
29
|
+
|
|
30
|
+
def __setitem__(self, key: Any, value: Any) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Set attribute in Pipeline with provided key
|
|
33
|
+
:param Any key: Key to change to provided value
|
|
34
|
+
:param Any value: New value for provided Key
|
|
35
|
+
:rtype: None
|
|
36
|
+
"""
|
|
37
|
+
return setattr(self, key, value)
|