Skip to main content

A production-quality Open FAIR library for quantitative cyber risk analysis

Project description

pyCRQ

pyCRQ Hero

PyPI version Python License: MIT

A Python library for quantitative cyber risk analysis using the Open FAIR standard. Model threats as probability distributions, run Monte Carlo simulations, and get dollar-denominated risk estimates with full uncertainty ranges.


Table of Contents

  1. How FAIR Works
  2. Installation
  3. Your First Scenario in 5 Minutes
  4. Core Concepts
  5. ScenarioBuilder Reference
  6. Running Simulations
  7. Analysis & Reporting
  8. Common Recipes
  9. API Reference
  10. Examples

How FAIR Works

FAIR (Factor Analysis of Information Risk) breaks down cyber risk into two questions:

  1. How often will a loss event occur? → Loss Event Frequency (LEF)
  2. How much will it cost when it does? → Loss Magnitude (LM)

Multiply those together and you get Annual Loss Exposure (ALE) — the expected annual cost of the risk in dollars.

ALE  =  Loss Event Frequency  ×  Loss Magnitude

FAIR further decomposes each side of that equation:

                         ┌─────────────────────────────────────────────┐
                         │              FAIR Risk Model                 │
                         └─────────────────────────────────────────────┘

            Annual Loss Exposure (ALE)
                       │
          ┌────────────┴───────────────┐
          │                            │
  Loss Event Frequency           Loss Magnitude (LM)
        (LEF)                          │
          │                   ┌────────┴────────────┐
    ┌─────┴──────┐        Primary Loss          Secondary Loss
    │            │             │                     │
   TEF        Vuln    (Productivity, Response,  (SLEF × SLM)
    │            │     Replacement, Competitive
  ┌─┴──┐     ┌──┴──┐   Advantage, Fines, Reputation)
  CF  PoA  TCap  CS
Term Full Name What It Means
LEF Loss Event Frequency How many loss events occur per year
TEF Threat Event Frequency How often the threat attempts an action
CF Contact Frequency How often the threat contacts the asset
PoA Probability of Action Given contact, how likely the threat acts
Vuln Vulnerability Probability a threat event becomes a loss event
TCap Threat Capability Attacker skill (0–100 score)
CS Control Strength Defensive strength (0–100 score)
LM Loss Magnitude Financial cost per loss event
ALE Annual Loss Exposure Expected annual cost = LEF × LM

You don't need all of these. Most scenarios only need 3–4 inputs. See Frequency Inputs for how to choose.


Installation

pip install pycrq

From source:

git clone https://github.com/securemetrics/pyCRQ.git
cd pyCRQ
pip install -e .

Requirements: Python 3.9+, numpy, scipy, matplotlib (all installed automatically).


Your First Scenario in 5 Minutes

Here is a complete working example. Copy and run it.

from pycrq import ScenarioBuilder, FAIRSimulator, pert, scenario_report

# Step 1: Describe the scenario
scenario = (
    ScenarioBuilder("Ransomware Attack")
    .asset("Customer Database", "Information")
    .threat("External Hacker", "External Adversarial")

    # Step 2: How often? (TEF = attempts per year, Vuln = % of attempts that succeed)
    .tef(pert(low=1, mode=3, high=8))             # 1 to 8 attacks per year
    .vulnerability(pert(low=0.10, mode=0.25, high=0.55))  # 10–55% success rate

    # Step 3: How much does it cost per event?
    .primary_loss(pert(low=50_000, mode=250_000, high=1_500_000))

    .build()
)

# Step 4: Simulate
simulator = FAIRSimulator(n_simulations=10_000, seed=42)
result = simulator.simulate(scenario)

# Step 5: Read the results
print(scenario_report(result))
print(f"\nMean ALE:   ${result.mean_ale:,.0f}")
print(f"90th pct:   ${result.percentile(90):,.0f}")
print(f"VaR (95%):  ${result.var(0.95):,.0f}")

Output looks like:

============================================================
 FAIR Simulation Report — Ransomware Attack
============================================================
 Simulations:    10,000
 Mean ALE:       $245,312
 Median ALE:     $142,891
 P90 ALE:        $621,440
 VaR (95%):      $788,230
 CVaR (95%):     $1,104,560
 Risk Level:     HIGH
============================================================

That's it. The rest of this guide explains how to build more detailed and accurate models.


Core Concepts

Distributions — Expressing Uncertainty

In FAIR, every input is a range, not a single number. You express uncertainty using probability distributions. The most common one is pert().

The PERT Distribution (Start Here)

pert(low, mode, high) is perfect for expert estimates: "I think it's usually around X, but could be as low as Y or as high as Z."

from pycrq import pert

# Threat event frequency: usually 3/year, but anywhere from 1 to 8
tef = pert(low=1, mode=3, high=8)

# Vulnerability: usually 25%, but could be 10% to 55%
vuln = pert(low=0.10, mode=0.25, high=0.55)

# Loss magnitude: usually $250K, but could be $50K to $1.5M
loss = pert(low=50_000, mode=250_000, high=1_500_000)

All Available Distributions

Distribution Function Best For
PERT pert(low, mode, high) Expert elicitation — "optimistic / most-likely / pessimistic"
Triangular triangular(low, mode, high) Like PERT but with sharper edges; use when the tails matter more
Uniform uniform(low, high) Complete uncertainty between two bounds
Normal normal(mean, std) Symmetric uncertainty around a known mean
LogNormal lognormal(meanlog, sdlog) Right-skewed costs and frequencies (never goes negative)
Beta beta(alpha, beta) Probabilities (0–1); flexible shape
Poisson poisson(lam) Discrete event counts when you know the expected annual rate
Constant constant(value) Fix a factor to a known value (e.g., during sensitivity analysis)
from pycrq import pert, normal, lognormal, uniform, triangular, constant, beta, poisson
from pycrq import LogNormalDistribution

# PERT — workhorse for expert estimates
freq = pert(1, 3, 8)

# Normal — symmetric; good for aggregated costs
cost = normal(mean=500_000, std=100_000)

# LogNormal — right-skewed; great for breach costs that can spike high
cost = LogNormalDistribution.from_moments(mean=500_000, std=300_000)

# Uniform — "anywhere in this range is equally likely"
vuln = uniform(low=0.1, high=0.4)

# Triangular — sharper peak than PERT
freq = triangular(low=1, mode=4, high=12)

# Constant — pin a factor to an exact value
exact = constant(value=50_000)

# Beta — vulnerability as a 0–1 probability with flexible skew
vuln = beta(alpha=2, beta=5)  # skewed toward low values

# Poisson — discrete event counts; mean=3.5 events/year
freq = poisson(lam=3.5)

Quick tip: All distributions expose .mean(), .std(), .percentile(p), and .sample(n) so you can inspect them before simulating:

d = pert(1, 3, 8)
print(d.mean())          # 3.0
print(d.percentile(90))  # ~5.8
print(d.sample(5))       # array([2.1, 4.7, 3.3, 1.8, 6.2])

Frequency Inputs — How Often Does It Happen?

This is the most flexible part of the model. You can provide frequency at three different levels of detail. Pick whichever fits your data.

Option A — Direct LEF (simplest)

Use .lef() when you have a single estimate of how many loss events occur per year — you've already folded in the probability of success.

scenario = (
    ScenarioBuilder("Phishing Campaign")
    .asset("Email System", "System")
    .threat("Phishing Group", "External Adversarial")
    .lef(pert(0.5, 1.5, 4))   # 0.5 to 4 successful phishing incidents/year
    .primary_loss(pert(10_000, 80_000, 400_000))
    .build()
)

⚠️ Trade-off: .lef() is quick to model, but bypasses TEF and Vulnerability entirely, so per-factor sensitivity analysis (e.g., "what if we improved our controls?") is unavailable. Use .tef() + .vulnerability() when you want to model control effectiveness.


Option B — TEF + Vulnerability (recommended)

This is the standard FAIR approach. You separately model how often attacks are attempted and how often they succeed.

scenario = (
    ScenarioBuilder("Credential Stuffing")
    .asset("Login Portal", "System")
    .threat("Bot Network", "External Adversarial")
    .tef(pert(50, 200, 1000))         # 50–1,000 attempts per year
    .vulnerability(pert(0.01, 0.05, 0.15))  # 1–15% succeed
    .primary_loss(pert(5_000, 30_000, 200_000))
    .build()
)

Option C — Full Decomposition (most detail)

Break TEF down into Contact Frequency × Probability of Action, and model Vulnerability from Threat Capability vs. Control Strength scores (0–100). Best for comparing control investment scenarios.

scenario = (
    ScenarioBuilder("Insider Threat")
    .asset("Financial Records", "Information")
    .threat("Malicious Insider", "Internal Adversarial")
    .contact_frequency(pert(10, 30, 60))         # contacts/year
    .probability_of_action(pert(0.05, 0.15, 0.30))  # acts on 5–30% of contacts
    .threat_capability(pert(30, 55, 75))          # attacker skill score
    .control_strength(pert(50, 65, 80))           # control effectiveness score
    .primary_loss(pert(100_000, 400_000, 2_000_000))
    .build()
)

When both threat_capability and control_strength are provided, vulnerability is computed via a logistic function:

Vuln = sigmoid(6 × (ThreatCapability − ControlStrength) / 100)

This means if your controls are stronger than the threat (CS > TCap), vulnerability drops sharply — and vice versa.

Priority Order

The simulator uses the first complete input it finds:

LEF  →  TEF  →  CF × PoA  →  CF alone

For vulnerability:

direct vulnerability  →  TCap + CS (logistic)  →  TCap alone  →  CS alone  →  Uniform(0,1)

Loss Inputs — How Much Does It Cost?

Option A — Single primary loss

The simplest approach — one distribution covers the total cost per event:

.primary_loss(pert(50_000, 250_000, 1_500_000))

Option B — By loss category

Break loss into FAIR's six primary categories. These are summed automatically:

.loss_productivity(pert(50_000, 200_000, 750_000))    # Revenue/output lost
.loss_response(pert(30_000, 100_000, 400_000))         # IR, forensics, legal
.loss_replacement(pert(10_000, 50_000, 200_000))       # Hardware/software replacement
.loss_competitive_advantage(pert(0, 25_000, 150_000))  # Market position impact
.loss_fines_judgments(pert(0, 75_000, 500_000))        # Regulatory fines
.loss_reputation(pert(0, 50_000, 300_000))             # Brand damage

Adding Secondary Loss

Secondary loss models costs triggered by external parties reacting to the event (e.g., regulators, customers, press):

.secondary_loss(
    slef=pert(0.10, 0.25, 0.50),          # probability secondary event occurs
    slm=pert(100_000, 500_000, 3_000_000) # cost when it does
)

ScenarioBuilder Reference

All methods return self for chaining. Call .build() at the end.

Asset & Threat

Method Description
.asset(name, asset_type, description) Define the asset at risk. asset_type: "Information", "System", "Service", "Physical", "Person", "Other"
.with_asset(asset_obj) Attach a pre-built Asset object
.threat(name, threat_type, description) Define the threat agent. threat_type: "External Adversarial", "Internal Adversarial", "External Non-Adversarial", "Internal Non-Adversarial", "Natural"
.with_threat(threat_obj) Attach a pre-built ThreatAgent object
.add_control(name, description, control_type) Document a control (e.g., "Preventive", "Detective")

Metadata

Method Description
.describe(text) Free-text narrative for the scenario
.tag("tag1", "tag2") Add string tags for filtering/grouping
.effect("Confidentiality") CIA impact type: "Confidentiality", "Integrity", "Availability", "Safety"
.time_horizon(years) Simulation time window in years (default 1.0)

Frequency

Method Use When Notes
.lef(dist) You have a direct estimate of annual loss events Bypasses TEF/Vuln; no per-factor sensitivity analysis
.tef(dist) You know how often attacks are attempted Combine with .vulnerability()
.contact_frequency(dist) You know how often the threat encounters the asset Multiply by .probability_of_action() to get TEF
.probability_of_action(dist) Given contact, probability the threat acts Used with .contact_frequency()
.threat_capability(dist) Attacker skill as a 0–100 score Combined with .control_strength() via logistic function
.control_strength(dist) Control effectiveness as a 0–100 score Combined with .threat_capability() via logistic function
.vulnerability(dist) Direct probability (0–1) that an attempt succeeds Simpler than TCap/CS; use when you can estimate it directly

Loss Magnitude

Method Description
.primary_loss(dist) Total primary loss per event (alternative to individual categories)
.loss_productivity(dist) Revenue or output lost
.loss_response(dist) Incident response, forensics, legal costs
.loss_replacement(dist) Asset repair or replacement costs
.loss_competitive_advantage(dist) Impact on market position
.loss_fines_judgments(dist) Regulatory fines and legal judgments
.loss_reputation(dist) Brand and reputation damage
.secondary_loss(slef, slm) Secondary loss event frequency + magnitude

Running Simulations

from pycrq import FAIRSimulator

simulator = FAIRSimulator(
    n_simulations=10_000,  # more = more stable results; 10K is a good default
    seed=42,               # set for reproducibility; omit for random results
)

# Single scenario
result = simulator.simulate(scenario)

# Entire risk register
agg = simulator.simulate_register(register)

Reading Results

# Key statistics
result.mean_ale              # mean annual loss exposure
result.std_ale               # standard deviation
result.percentile(10)        # 10th percentile ALE
result.percentile(50)        # median ALE
result.percentile(90)        # 90th percentile ALE
result.var(0.95)             # Value at Risk (95%)
result.cvar(0.95)            # Conditional VaR / Expected Shortfall (95%)

# Raw arrays (one value per simulation)
result.annual_loss_exposure  # np.ndarray, shape (n_simulations,)
result.loss_event_frequency  # np.ndarray
result.loss_magnitude        # np.ndarray
result.threat_event_frequency  # np.ndarray (None if .lef() was used)
result.vulnerability           # np.ndarray (None if .lef() was used)

# Full summary dict
result.summary()

Analysis & Reporting

Print a Report

from pycrq import scenario_report, register_report

print(scenario_report(result))              # single scenario
print(scenario_report(result, verbose=True))  # includes per-category breakdown
print(register_report(agg))                # full risk register

Compute Metrics

from pycrq import compute_metrics, classify_risk_level, format_currency

metrics = compute_metrics(result)
print(metrics.mean_ale)
print(metrics.var_95)
print(metrics.risk_level)   # "LOW" | "MEDIUM" | "HIGH" | "CRITICAL"

# Classify any dollar value
level = classify_risk_level(250_000)   # "MEDIUM"

# Format numbers for display
format_currency(1_500_000)   # "$1.50M"
format_currency(85_000)      # "$85.0K"

Compare Scenarios Side-by-Side

from pycrq import rank_scenarios, summary_table

# Rank by mean ALE (highest first)
ranked = rank_scenarios(results, by="mean_ale")
for name, value in ranked:
    print(f"{name}: {format_currency(value)}")

# ASCII comparison table
print(summary_table(results))

Validate Before You Simulate

from pycrq import validate_scenario

warnings = validate_scenario(scenario)
for w in warnings:
    print(f"[WARN] {w}")

This catches common issues like missing frequency inputs, unrealistic vulnerability values, and missing secondary loss definitions.

Plots

from pycrq import plot_ale_distribution, plot_scenario_comparison, plot_risk_matrix

# ALE histogram with percentile markers
fig = plot_ale_distribution(result, title="Ransomware ALE", show_percentiles=True)
fig.savefig("ale_distribution.png", dpi=150)

# Bar chart comparing multiple scenarios
fig = plot_scenario_comparison(results, metric="mean_ale")

# Risk matrix
fig = plot_risk_matrix(results)

Common Recipes

Risk Register (Multiple Scenarios)

Model an entire threat landscape and analyze the portfolio.

from pycrq import RiskRegister, FAIRSimulator, register_report, rank_scenarios

register = RiskRegister(name="Enterprise Cyber 2026")

register.add_scenario(
    ScenarioBuilder("Ransomware")
    .asset("File Servers", "System")
    .threat("Ransomware Gang", "External Adversarial")
    .tef(pert(0.5, 1, 3))
    .vulnerability(pert(0.2, 0.4, 0.7))
    .primary_loss(pert(200_000, 800_000, 5_000_000))
    .build()
)

register.add_scenario(
    ScenarioBuilder("Phishing — Credential Theft")
    .asset("Email / Identity", "Information")
    .threat("Phishing Group", "External Adversarial")
    .tef(pert(5, 15, 40))
    .vulnerability(pert(0.02, 0.05, 0.12))
    .primary_loss(pert(10_000, 60_000, 300_000))
    .build()
)

register.add_scenario(
    ScenarioBuilder("Insider Data Theft")
    .asset("Customer Records", "Information")
    .threat("Malicious Employee", "Internal Adversarial")
    .lef(pert(0.1, 0.3, 1))   # historical rate — skip TEF/Vuln decomposition
    .primary_loss(pert(50_000, 250_000, 1_000_000))
    .secondary_loss(
        slef=pert(0.2, 0.4, 0.7),
        slm=pert(100_000, 500_000, 2_000_000)
    )
    .build()
)

simulator = FAIRSimulator(n_simulations=10_000, seed=42)
agg = simulator.simulate_register(register)

print(register_report(agg))

# Rank scenarios by mean ALE
from pycrq import rank_scenarios
ranked = rank_scenarios(agg.scenario_results, by="mean_ale")
for name, value in ranked:
    print(f"  {name}: ${value:,.0f}")

Control ROI / ROSI

Compare risk before and after a control, then calculate return on security investment.

from pycrq import ScenarioBuilder, FAIRSimulator, control_roi, compute_risk_reduction, pert

simulator = FAIRSimulator(n_simulations=10_000, seed=42)

# Before: no MFA
before = (
    ScenarioBuilder("Account Takeover — No MFA")
    .asset("Customer Portal", "System")
    .threat("Credential Stuffers", "External Adversarial")
    .tef(pert(20, 60, 200))
    .vulnerability(pert(0.05, 0.15, 0.35))
    .primary_loss(pert(5_000, 40_000, 200_000))
    .build()
)

# After: MFA deployed — stronger controls, lower vulnerability
after = (
    ScenarioBuilder("Account Takeover — With MFA")
    .asset("Customer Portal", "System")
    .threat("Credential Stuffers", "External Adversarial")
    .tef(pert(20, 60, 200))
    .vulnerability(pert(0.01, 0.03, 0.08))   # MFA cuts success rate dramatically
    .primary_loss(pert(5_000, 40_000, 200_000))
    .build()
)

before_result = simulator.simulate(before)
after_result  = simulator.simulate(after)

# Risk reduction
reduction = compute_risk_reduction(before_result, after_result)
print(f"ALE reduction: ${reduction['mean_ale_reduction_abs']:,.0f}")
print(f"Reduction %:   {reduction['mean_ale_reduction_pct']:.1f}%")

# ROSI — MFA costs $60K/year to operate
roi = control_roi(before_result, after_result, control_cost=60_000)
print(f"ROSI:            {roi['rosi_pct']:.0f}%")
print(f"Net benefit:     ${roi['net_benefit']:,.0f}")
print(f"Break-even:      {roi['break_even_years']:.1f} years")
print(f"Recommended:     {roi['recommended']}")

Sensitivity Analysis

Sweep a parameter across a range to see how much it drives your ALE. Useful for prioritizing control investments.

from pycrq import ScenarioBuilder, FAIRSimulator, sensitivity_analysis, pert, constant

def build_scenario(control_strength_value):
    return (
        ScenarioBuilder("Phishing — Control Sweep")
        .asset("Email", "Information")
        .threat("Phisher", "External Adversarial")
        .contact_frequency(pert(10, 30, 80))
        .probability_of_action(pert(0.2, 0.4, 0.7))
        .threat_capability(pert(40, 60, 80))
        .control_strength(constant(control_strength_value))  # sweep this
        .primary_loss(pert(10_000, 75_000, 400_000))
        .build()
    )

results = sensitivity_analysis(
    scenario_factory=build_scenario,
    param_name="control_strength",
    param_values=[20, 40, 60, 80, 95],
    n_simulations=5_000,
    seed=42,
)

for r in results:
    cs = r.scenario_name.split("=")[-1]
    print(f"Control Strength {cs}: Mean ALE = ${r.mean_ale:,.0f}")

Bootstrap Confidence Intervals

Quantify how much uncertainty exists in your simulation estimates themselves.

from pycrq import bootstrap_confidence_interval

lower, upper = bootstrap_confidence_interval(
    result,
    statistic="mean",    # or "p90", "var_95"
    confidence=0.95,
    n_bootstrap=1_000,
)
print(f"Mean ALE 95% CI: ${lower:,.0f} – ${upper:,.0f}")

Calibrate from Expert P10/P90

If a SME gives you confidence bounds rather than a three-point estimate, use calibrate_pert_from_confidence to convert them:

from pycrq import calibrate_pert_from_confidence, pert

# "I'm 90% sure the loss is between $50K and $2M"
low, mode, high = calibrate_pert_from_confidence(p10=50_000, p90=2_000_000)
loss_dist = pert(low, mode, high)

Export to JSON or CSV

from pycrq import export_json, export_csv

export_json(result, "outputs/ransomware.json")
export_csv(result, "outputs/ransomware.csv")

# The JSON captures all metadata and is round-trippable

API Reference

Distributions

Function Parameters Notes
pert(low, mode, high, lam=4.0) low ≤ mode ≤ high Best default choice for expert estimates
normal(mean, std) std ≥ 0 Symmetric; can go negative — use for aggregated costs
lognormal(meanlog, sdlog) sdlog > 0 Log-space params; use from_moments() for real-space
uniform(low, high) high ≥ low Flat uncertainty
triangular(low, mode, high) low ≤ mode ≤ high Like PERT with less weight on mode
constant(value) any float Pins a factor to an exact value
beta(alpha, beta, low=0, high=1) alpha, beta > 0 Flexible 0–1 probability shape
poisson(lam) lam > 0 Discrete counts; mean = variance = lam
from_dict(d) dict with "type" key Deserialize any distribution from its to_dict() output
LogNormalDistribution.from_moments(mean, std) mean, std > 0 Construct from real-space moments

FAIRSimulator

FAIRSimulator(n_simulations=10_000, seed=None)

.simulate(scenario)           # → SimulationResult
.simulate_register(register)  # → AggregateSimulationResult

SimulationResult:

Attribute / Method Type Description
annual_loss_exposure np.ndarray ALE per simulation
loss_event_frequency np.ndarray LEF per simulation
loss_magnitude np.ndarray LM per simulation
threat_event_frequency np.ndarray | None TEF (None when .lef() path used)
vulnerability np.ndarray | None Vuln (None when .lef() path used)
primary_loss np.ndarray Primary loss component
secondary_loss np.ndarray | None Secondary loss (None if not modelled)
loss_by_category dict Per-category arrays when using category methods
mean_ale float Mean ALE
std_ale float Standard deviation of ALE
.percentile(p) float p-th percentile of ALE (0–100)
.var(confidence) float Value-at-Risk
.cvar(confidence) float Conditional VaR / Expected Shortfall
.summary() dict All key statistics as a dict

Analysis Functions

from pycrq import (
    compute_metrics,           # → RiskMetrics from SimulationResult
    classify_risk_level,       # classify_risk_level(mean_ale) → "LOW"|"MEDIUM"|"HIGH"|"CRITICAL"
    rank_scenarios,            # rank_scenarios(results, by="mean_ale") → [(name, value), ...]
    compute_risk_reduction,    # compute_risk_reduction(before, after) → dict
    control_roi,               # control_roi(before, after, control_cost) → dict
    sensitivity_analysis,      # sensitivity_analysis(factory_fn, param_name, values, ...) → list
    bootstrap_confidence_interval,  # (result, statistic, confidence, n_bootstrap) → (lower, upper)
)

Utilities

from pycrq import (
    validate_scenario,               # → List[str] of advisory warnings
    calibrate_pert_from_confidence,  # (p10, p90) → (low, mode, high)
    frequency_to_rate,               # "monthly" → 12.0, "weekly" → 52.0, etc.
    annualize,                       # annualize(5_000, "monthly") → 60_000
    summary_table,                   # summary_table(results) → ASCII str
)

Examples

Ready-to-run examples are in the examples/ directory:

File What It Shows
examples/01_basic_scenario.py Full ransomware scenario — PERT distributions, full report, bootstrap CI
examples/02_risk_register.py 5-scenario risk register, aggregate portfolio, rankings
examples/03_control_roi.py Before/after MFA comparison, ROSI, break-even analysis
examples/04_sensitivity_analysis.py Threat capability sweep 20→80, control strength sweep, logistic vulnerability
python examples/01_basic_scenario.py
python examples/02_risk_register.py
python examples/03_control_roi.py
python examples/04_sensitivity_analysis.py

License

MIT License. See LICENSE for details.

Open FAIR is a standard published by The Open Group. This library is an independent implementation and is not affiliated with or endorsed by The Open Group.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pycrq-1.1.0.tar.gz (49.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pycrq-1.1.0-py3-none-any.whl (41.7 kB view details)

Uploaded Python 3

File details

Details for the file pycrq-1.1.0.tar.gz.

File metadata

  • Download URL: pycrq-1.1.0.tar.gz
  • Upload date:
  • Size: 49.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for pycrq-1.1.0.tar.gz
Algorithm Hash digest
SHA256 1a8739afaad33e491ea6ddf5bd51df4299654d34367a2b0fd4b969cbdca9263a
MD5 f78d0ec5dda7ab00a5753610b378a7d0
BLAKE2b-256 caed099d3379ccefe3e6121f059e361db2e0a611e4f1b0b9c145a75409206c64

See more details on using hashes here.

File details

Details for the file pycrq-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: pycrq-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 41.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for pycrq-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4ef5de891a35df24569f0dba0de9a4d4395431da667be2d4c5fc0246a7b7a3e5
MD5 0c59566c76d8713f92985a9ad10b2270
BLAKE2b-256 a487255c15368f4f34918d7ff47b7a23abf983b7d4a74c9541d24bf309a76914

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page