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,459 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""RegScale File Comparison"""
|
|
4
|
+
|
|
5
|
+
# standard python imports
|
|
6
|
+
from typing import TYPE_CHECKING, Optional
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
import pandas as pd # Type Checking
|
|
10
|
+
from datetime import datetime, timedelta
|
|
11
|
+
from os.path import exists
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Tuple
|
|
14
|
+
from rich.progress import TaskID
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
|
|
18
|
+
from regscale.core.app.api import Api
|
|
19
|
+
from regscale.core.app.utils.app_utils import (
|
|
20
|
+
check_license,
|
|
21
|
+
create_progress_object,
|
|
22
|
+
error_and_exit,
|
|
23
|
+
get_current_datetime,
|
|
24
|
+
get_file_name,
|
|
25
|
+
get_file_type,
|
|
26
|
+
get_recent_files,
|
|
27
|
+
)
|
|
28
|
+
from regscale.core.app.utils.regscale_utils import verify_provided_module
|
|
29
|
+
from regscale.models.app_models.click import NotRequiredIf, regscale_id, regscale_module
|
|
30
|
+
from regscale.models.regscale_models import Assessment, File
|
|
31
|
+
|
|
32
|
+
job_progress = create_progress_object()
|
|
33
|
+
XLSX = ".xlsx"
|
|
34
|
+
CSV = ".csv"
|
|
35
|
+
XLS = ".xls"
|
|
36
|
+
SUPPORTED_FILE_TYPES = [CSV, XLSX, XLS]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@click.group()
|
|
40
|
+
def compare():
|
|
41
|
+
"""Create RegScale Assessment of differences after comparing two files."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@compare.command(name="compare_files")
|
|
45
|
+
@click.option(
|
|
46
|
+
"--most_recent_in_file_path",
|
|
47
|
+
type=click.Path(exists=True, dir_okay=True, file_okay=False, path_type=Path),
|
|
48
|
+
help="Grab two most recent files in the provided directory for comparison.",
|
|
49
|
+
default=None,
|
|
50
|
+
cls=NotRequiredIf,
|
|
51
|
+
not_required_if=["old_file", "new_file"],
|
|
52
|
+
)
|
|
53
|
+
@click.option(
|
|
54
|
+
"--most_recent_file_type",
|
|
55
|
+
type=click.Choice([".csv", XLSX], case_sensitive=False),
|
|
56
|
+
help="Filter the directory for .csv or .xlsx file types.",
|
|
57
|
+
default=None,
|
|
58
|
+
cls=NotRequiredIf,
|
|
59
|
+
not_required_if=["old_file", "new_file"],
|
|
60
|
+
)
|
|
61
|
+
@click.option(
|
|
62
|
+
"--old_file",
|
|
63
|
+
type=click.Path(exists=True, dir_okay=False, file_okay=True, path_type=Path),
|
|
64
|
+
help=(
|
|
65
|
+
"Enter file path of the original file to compare: must be used with --new_file, "
|
|
66
|
+
+ "not required if --most_recent_in_file_path & --most_recent_file_type is used."
|
|
67
|
+
),
|
|
68
|
+
cls=NotRequiredIf,
|
|
69
|
+
not_required_if=["most_recent_in_file_path", "most_recent_file_type"],
|
|
70
|
+
)
|
|
71
|
+
@click.option(
|
|
72
|
+
"--new_file",
|
|
73
|
+
type=click.Path(exists=True, dir_okay=False, file_okay=True, path_type=Path),
|
|
74
|
+
help=(
|
|
75
|
+
"Enter file path of new file to compare: must be used with --old_file, "
|
|
76
|
+
+ "not required if --most_recent_in_file_path & --most_recent_file_type is used."
|
|
77
|
+
),
|
|
78
|
+
cls=NotRequiredIf,
|
|
79
|
+
not_required_if=["most_recent_in_file_path", "most_recent_file_type"],
|
|
80
|
+
)
|
|
81
|
+
@click.option(
|
|
82
|
+
"--key",
|
|
83
|
+
type=click.STRING,
|
|
84
|
+
help="Enter unique key to compare the files.",
|
|
85
|
+
prompt="Enter the key/column to compare files",
|
|
86
|
+
required=True,
|
|
87
|
+
)
|
|
88
|
+
@regscale_id()
|
|
89
|
+
@regscale_module()
|
|
90
|
+
def compare_files_cli(
|
|
91
|
+
old_file: str,
|
|
92
|
+
new_file: str,
|
|
93
|
+
most_recent_in_file_path: Path,
|
|
94
|
+
most_recent_file_type: str,
|
|
95
|
+
key: str,
|
|
96
|
+
regscale_id: int,
|
|
97
|
+
regscale_module: str,
|
|
98
|
+
):
|
|
99
|
+
"""Compare the two given files while using the provided key for any differences.
|
|
100
|
+
Supports csv, xls and xlsx files."""
|
|
101
|
+
compare_files(
|
|
102
|
+
old_file=old_file,
|
|
103
|
+
new_file=new_file,
|
|
104
|
+
most_recent_in_file_path=most_recent_in_file_path,
|
|
105
|
+
most_recent_file_type=most_recent_file_type,
|
|
106
|
+
key=key,
|
|
107
|
+
parent_id=regscale_id,
|
|
108
|
+
parent_module=regscale_module,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def compare_files(
|
|
113
|
+
key: str,
|
|
114
|
+
parent_id: int,
|
|
115
|
+
parent_module: str,
|
|
116
|
+
old_file: Optional[str] = None,
|
|
117
|
+
new_file: Optional[str] = None,
|
|
118
|
+
most_recent_in_file_path: Optional[Path] = None,
|
|
119
|
+
most_recent_file_type: Optional[str] = None,
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Compare the two given files while using the provided key for any differences
|
|
122
|
+
|
|
123
|
+
:param str key: Unique key to compare the files
|
|
124
|
+
:param int parent_id: ID of the RegScale Module
|
|
125
|
+
:param str parent_module: Name of the RegScale Module
|
|
126
|
+
:param str old_file: File path of the original file to compare
|
|
127
|
+
:param str new_file: File path of new file to compare
|
|
128
|
+
:param Path most_recent_in_file_path: Path to the directory to find the two most recent files
|
|
129
|
+
:param str most_recent_file_type: File type to filter the directory for
|
|
130
|
+
:rtype: None
|
|
131
|
+
"""
|
|
132
|
+
app = check_license()
|
|
133
|
+
api = Api()
|
|
134
|
+
|
|
135
|
+
# see if provided RegScale Module is an accepted option
|
|
136
|
+
verify_provided_module(parent_module)
|
|
137
|
+
|
|
138
|
+
# see if most_recent_in argument was used, get the old and new file
|
|
139
|
+
if most_recent_in_file_path:
|
|
140
|
+
# get the two most_recent_file_type in the provided most_recent_in_file_path
|
|
141
|
+
recent_files = get_recent_files(
|
|
142
|
+
file_path=most_recent_in_file_path,
|
|
143
|
+
file_count=2,
|
|
144
|
+
file_type=most_recent_file_type,
|
|
145
|
+
)
|
|
146
|
+
# verify we have two files to compare
|
|
147
|
+
if len(recent_files) == 2:
|
|
148
|
+
# set the old_file and new_file accordingly
|
|
149
|
+
old_file = recent_files[1]
|
|
150
|
+
new_file = recent_files[0]
|
|
151
|
+
else:
|
|
152
|
+
# notify user we don't have two files to compare and exit application
|
|
153
|
+
error_and_exit(
|
|
154
|
+
f"Required 2 files to compare, but only 1 {most_recent_file_type}"
|
|
155
|
+
f"file found in {most_recent_in_file_path}!"
|
|
156
|
+
)
|
|
157
|
+
# make sure both file paths exist
|
|
158
|
+
if not exists(old_file) and not exists(new_file):
|
|
159
|
+
error_and_exit("Please check the file paths of the provided files and try again.")
|
|
160
|
+
with job_progress:
|
|
161
|
+
# check the file extensions and compare them
|
|
162
|
+
old_file_type, new_file_type = get_file_type(old_file), get_file_type(new_file)
|
|
163
|
+
file_type = None
|
|
164
|
+
for supported_file_type in SUPPORTED_FILE_TYPES:
|
|
165
|
+
if old_file_type == new_file_type:
|
|
166
|
+
file_type = supported_file_type
|
|
167
|
+
if not file_type:
|
|
168
|
+
error_and_exit(
|
|
169
|
+
f"{old_file_type or new_file_type} files are not a supported file type provided for comparison."
|
|
170
|
+
)
|
|
171
|
+
# get the file names of from the provided file paths
|
|
172
|
+
old_file_name, new_file_name = get_file_name(old_file), get_file_name(new_file)
|
|
173
|
+
|
|
174
|
+
# create task for progress bar
|
|
175
|
+
comparing = job_progress.add_task(f"[#f8b737]Comparing {file_type} files for differences...", total=1)
|
|
176
|
+
output, old_row_count, new_row_count = comparison(old_file, new_file, key, file_type)
|
|
177
|
+
|
|
178
|
+
# mark the comparing task as complete
|
|
179
|
+
job_progress.update(comparing, advance=1)
|
|
180
|
+
|
|
181
|
+
# create task for formatting data
|
|
182
|
+
formatting = job_progress.add_task(
|
|
183
|
+
"[#ef5d23]Formatting data of comparison outcome...",
|
|
184
|
+
total=1,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# drop any rows that has no value for the provided key
|
|
188
|
+
output = output.dropna(subset=[key])
|
|
189
|
+
|
|
190
|
+
# check if there were any changes, if no changes the assessment
|
|
191
|
+
# will be created with complete as the status
|
|
192
|
+
if output.empty:
|
|
193
|
+
status = "Complete"
|
|
194
|
+
actual_finish = get_current_datetime()
|
|
195
|
+
report = f"<h3>No differences between {old_file_name} & {new_file_name}</h3>"
|
|
196
|
+
else:
|
|
197
|
+
status = "Scheduled"
|
|
198
|
+
actual_finish = False
|
|
199
|
+
# format report string for assessment
|
|
200
|
+
report = (
|
|
201
|
+
f"<h3>{old_file_name} Deleted Rows</h3>"
|
|
202
|
+
f"{create_filtered_html_table(output, 'flag', 'deleted')}"
|
|
203
|
+
f"<h3>{new_file_name} Added Rows</h3>"
|
|
204
|
+
f"{create_filtered_html_table(output, 'flag', 'added')}"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# get data overview
|
|
208
|
+
overview = create_overview(
|
|
209
|
+
data=output,
|
|
210
|
+
old_row_count=old_row_count,
|
|
211
|
+
new_row_count=new_row_count,
|
|
212
|
+
old_file=old_file_name,
|
|
213
|
+
new_file=new_file_name,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# set up descript for assessment
|
|
217
|
+
desc = (
|
|
218
|
+
f"Comparing two {file_type} files using {key} as a key.<br>"
|
|
219
|
+
f"<b>{old_file_name}</b> contains <b>{old_row_count}</b> row(s) and<br>"
|
|
220
|
+
f"<b>{new_file_name}</b> contains <b>{new_row_count}</b> row(s)<br>{overview}"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# create new task for creating assessment in RegScale
|
|
224
|
+
create_assessment_task = job_progress.add_task(
|
|
225
|
+
"[#21a5bb]Creating assessment in RegScale...",
|
|
226
|
+
total=1,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if created_assessment := create_assessment(
|
|
230
|
+
config=app.config,
|
|
231
|
+
file_type=file_type,
|
|
232
|
+
desc=desc,
|
|
233
|
+
parent_id=parent_id,
|
|
234
|
+
parent_module=parent_module,
|
|
235
|
+
report=report,
|
|
236
|
+
status=status,
|
|
237
|
+
formatting=formatting,
|
|
238
|
+
actual_finish=actual_finish,
|
|
239
|
+
):
|
|
240
|
+
# mark the create_assessment task as complete
|
|
241
|
+
job_progress.update(create_assessment_task, advance=1)
|
|
242
|
+
|
|
243
|
+
# create new task for file uploads
|
|
244
|
+
upload_files = job_progress.add_task(
|
|
245
|
+
"[#0866b4]Uploading files to the new RegScale Assessment...",
|
|
246
|
+
total=2,
|
|
247
|
+
)
|
|
248
|
+
upload_files_to_assessment(
|
|
249
|
+
api=api,
|
|
250
|
+
assessment_id=created_assessment.id,
|
|
251
|
+
file_paths=[old_file, new_file],
|
|
252
|
+
upload_files_task=upload_files,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# notify user assessment was created and output a link to it
|
|
256
|
+
app.logger.info(
|
|
257
|
+
"New assessment has been created and marked as %s: %s/form/assessments/%s",
|
|
258
|
+
status,
|
|
259
|
+
app.config["domain"].rstrip("/"),
|
|
260
|
+
created_assessment.id,
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
# notify the user we were unable to create the assessment int RegScale
|
|
264
|
+
error_and_exit("Unable to create new RegScale Assessment!")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def upload_files_to_assessment(api: Api, assessment_id: int, file_paths: list[str], upload_files_task: TaskID):
|
|
268
|
+
"""
|
|
269
|
+
Function to upload files to a RegScale Assessment
|
|
270
|
+
|
|
271
|
+
:param Api api: RegScale API object
|
|
272
|
+
:param int assessment_id: ID of the RegScale Assessment
|
|
273
|
+
:param list[str] file_paths: List of file paths to upload
|
|
274
|
+
:param TaskID upload_files_task: TaskID for progress bar
|
|
275
|
+
:rtype: None
|
|
276
|
+
"""
|
|
277
|
+
upload_count = 0
|
|
278
|
+
for file in file_paths:
|
|
279
|
+
if File.upload_file_to_regscale(
|
|
280
|
+
file_name=file,
|
|
281
|
+
parent_id=assessment_id,
|
|
282
|
+
parent_module="assessments",
|
|
283
|
+
api=api,
|
|
284
|
+
):
|
|
285
|
+
upload_count += 1
|
|
286
|
+
job_progress.update(upload_files_task, advance=1)
|
|
287
|
+
if upload_count == 2:
|
|
288
|
+
api.logger.info("Files uploaded to the new assessment in RegScale successfully.")
|
|
289
|
+
else:
|
|
290
|
+
api.logger.error("Unable to upload both files to the assessment in RegScale.")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def create_assessment(
|
|
294
|
+
config: dict,
|
|
295
|
+
file_type: str,
|
|
296
|
+
desc: str,
|
|
297
|
+
parent_id: int,
|
|
298
|
+
parent_module: str,
|
|
299
|
+
report: str,
|
|
300
|
+
status: str,
|
|
301
|
+
formatting: TaskID,
|
|
302
|
+
actual_finish: Optional[str] = None,
|
|
303
|
+
) -> Assessment:
|
|
304
|
+
"""
|
|
305
|
+
Function to create a new Assessment in RegScale
|
|
306
|
+
|
|
307
|
+
:param dict config: Config file for the application
|
|
308
|
+
:param str file_type: Type of file being compared
|
|
309
|
+
:param str desc: Description of the comparison
|
|
310
|
+
:param int parent_id: ID of the parent object
|
|
311
|
+
:param str parent_module: Module of the parent object
|
|
312
|
+
:param str report: Report of the comparison
|
|
313
|
+
:param str status: Status of the comparison
|
|
314
|
+
:param TaskID formatting: TaskID for formatting task
|
|
315
|
+
:param Optional[str] actual_finish: Actual finish date of the assessment
|
|
316
|
+
:return: New Assessment object created in RegScale
|
|
317
|
+
:rtype: Assessment
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
# set up title for the new Assessment
|
|
321
|
+
title = f"{file_type} Comparison for {parent_module.title()}-{parent_id}"
|
|
322
|
+
|
|
323
|
+
# set up plannedFinish date with days from config file
|
|
324
|
+
finish_date = (datetime.now() + timedelta(days=config["assessmentDays"])).strftime("%Y-%m-%dT%H:%M:%S")
|
|
325
|
+
|
|
326
|
+
# map to assessment dataclass
|
|
327
|
+
new_assessment = Assessment(
|
|
328
|
+
title=title,
|
|
329
|
+
assessmentType="Control Testing",
|
|
330
|
+
plannedStart=get_current_datetime(),
|
|
331
|
+
plannedFinish=finish_date,
|
|
332
|
+
assessmentReport=report,
|
|
333
|
+
assessmentPlan=desc,
|
|
334
|
+
dateCreated=get_current_datetime(),
|
|
335
|
+
dateLastUpdated=get_current_datetime(),
|
|
336
|
+
parentModule=parent_module,
|
|
337
|
+
parentId=parent_id,
|
|
338
|
+
status=status,
|
|
339
|
+
)
|
|
340
|
+
# update the appropriate fields to complete the assessment
|
|
341
|
+
if actual_finish:
|
|
342
|
+
new_assessment.actualFinish = actual_finish
|
|
343
|
+
new_assessment.assessmentResult = "Pass"
|
|
344
|
+
|
|
345
|
+
# mark the formatting task as complete
|
|
346
|
+
job_progress.update(formatting, advance=1)
|
|
347
|
+
|
|
348
|
+
return new_assessment.create()
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def comparison(file_one: str, file_two: str, key: str, file_type: str) -> Tuple["pd.DataFrame", int, int]:
|
|
352
|
+
"""
|
|
353
|
+
Function that will compare two files using the provided key, uses
|
|
354
|
+
a comparison method depending on the provided file_type and will
|
|
355
|
+
return the differences between the two files in a pandas dataframe
|
|
356
|
+
|
|
357
|
+
:param str file_one: Old file to compare
|
|
358
|
+
:param str file_two: New file to compare
|
|
359
|
+
:param str key: key field to compare the files on
|
|
360
|
+
:param str file_type: file type of the two files
|
|
361
|
+
:return: Tuple[difference between two files as panda's dataframe, # of rows in file_one, # of rows in file_two]
|
|
362
|
+
:rtype: Tuple[pd.DataFrame, int, int]
|
|
363
|
+
"""
|
|
364
|
+
import pandas as pd # Optimize import performance
|
|
365
|
+
|
|
366
|
+
if file_type.lower() not in [".csv", XLSX, ".xls"]:
|
|
367
|
+
error_and_exit("Unsupported file type provided for comparison.")
|
|
368
|
+
|
|
369
|
+
df1, df2 = [], []
|
|
370
|
+
if file_type.lower() == ".csv":
|
|
371
|
+
# open the files
|
|
372
|
+
df1 = pd.read_csv(file_one)
|
|
373
|
+
df2 = pd.read_csv(file_two)
|
|
374
|
+
elif file_type.lower() in [XLSX, ".xls"]:
|
|
375
|
+
# open the files
|
|
376
|
+
df1 = pd.read_excel(file_one)
|
|
377
|
+
df2 = pd.read_excel(file_two)
|
|
378
|
+
# add flags to each dataset
|
|
379
|
+
df1["flag"] = "deleted"
|
|
380
|
+
df2["flag"] = "added"
|
|
381
|
+
|
|
382
|
+
# combine the two datasets
|
|
383
|
+
df = pd.concat([df1, df2])
|
|
384
|
+
|
|
385
|
+
# get the differences between the two datasets
|
|
386
|
+
output = df.drop_duplicates(df.columns.difference(["flag", key]), keep=False)
|
|
387
|
+
|
|
388
|
+
# return the differences and the row counts for each file
|
|
389
|
+
return output, len(df1), len(df2)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def create_overview(
|
|
393
|
+
data: "pd.DataFrame",
|
|
394
|
+
old_row_count: int,
|
|
395
|
+
new_row_count: int,
|
|
396
|
+
old_file: str,
|
|
397
|
+
new_file: str,
|
|
398
|
+
) -> str:
|
|
399
|
+
"""
|
|
400
|
+
Function to create html formatted description from comparing
|
|
401
|
+
data from provided pandas dataframe and row counts
|
|
402
|
+
|
|
403
|
+
:param pd.DataFrame data: Pandas dataframe of data to format and filter
|
|
404
|
+
:param int old_row_count: Number of rows in the old file
|
|
405
|
+
:param int new_row_count: Number of rows in the new file
|
|
406
|
+
:param str old_file: Old file name
|
|
407
|
+
:param str new_file: New file name
|
|
408
|
+
:return: string of HTML formatted table of the provided data
|
|
409
|
+
:rtype: str
|
|
410
|
+
"""
|
|
411
|
+
# convert data frame to a series style dictionary
|
|
412
|
+
data = data.to_dict("series")
|
|
413
|
+
|
|
414
|
+
# create dictionary to store all the changes
|
|
415
|
+
changes = {"deletes": 0, "additions": 0}
|
|
416
|
+
|
|
417
|
+
# combine the flags and update the changes dictionary
|
|
418
|
+
for change in data["flag"].items():
|
|
419
|
+
if change[1] == "deleted":
|
|
420
|
+
changes["deletes"] += 1
|
|
421
|
+
elif change[1] == "added":
|
|
422
|
+
changes["additions"] += 1
|
|
423
|
+
|
|
424
|
+
# calculate % of rows deleted from old_file
|
|
425
|
+
percent_deleted = round(((old_row_count - changes["deletes"]) / old_row_count) * -100 + 100, 2)
|
|
426
|
+
|
|
427
|
+
# calculate % of rows added to new_file
|
|
428
|
+
percent_added = round(((new_row_count - changes["additions"]) / new_row_count) * -100 + 100, 2)
|
|
429
|
+
|
|
430
|
+
# format the html string with the changes and percentages
|
|
431
|
+
overview = f"<br>{changes['deletes']} row(s) deleted from {old_file}: ({percent_deleted}%)<br>"
|
|
432
|
+
overview += f"<br>{changes['additions']} row(s) added to {new_file}: ({percent_added}%)<br>"
|
|
433
|
+
|
|
434
|
+
# return the html formatted string
|
|
435
|
+
return overview
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def create_filtered_html_table(data: "pd.DataFrame", column: str, value: Any, pop_flag: bool = True) -> str:
|
|
439
|
+
"""
|
|
440
|
+
Function to return an HTML formatted table of data from the provided
|
|
441
|
+
pandas dataframe where the provided column == provided value
|
|
442
|
+
|
|
443
|
+
:param pd.DataFrame data: Data to create into an HTML table
|
|
444
|
+
:param str column: Column to filter the data on
|
|
445
|
+
:param Any value: Value to filter the data with
|
|
446
|
+
:param bool pop_flag: Whether to remove the column used to filter the data defaults to True
|
|
447
|
+
:return: String of HTML formatted table
|
|
448
|
+
:rtype: str
|
|
449
|
+
"""
|
|
450
|
+
# filter the provided pandas dataframe on the column and value provided
|
|
451
|
+
filtered_data = data.loc[data[column] == value]
|
|
452
|
+
|
|
453
|
+
# remove the field if requested, default is True
|
|
454
|
+
if pop_flag:
|
|
455
|
+
# remove the column from the dataset
|
|
456
|
+
filtered_data.pop(column)
|
|
457
|
+
|
|
458
|
+
# return HTML formatted data table
|
|
459
|
+
return None if filtered_data.empty else filtered_data.to_html(justify="left", index=False)
|