implement cli/infra update cicd
This commit is contained in:
144
.gitlab-ci.yml
144
.gitlab-ci.yml
@@ -4,49 +4,18 @@ stages:
|
|||||||
- lint
|
- lint
|
||||||
- test
|
- test
|
||||||
- build
|
- build
|
||||||
|
- deploy
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
|
|
||||||
UV_CACHE_DIR: "$CI_PROJECT_DIR/.uv-cache"
|
UV_CACHE_DIR: "$CI_PROJECT_DIR/.uv-cache"
|
||||||
|
|
||||||
# Cache dependencies between jobs
|
|
||||||
cache:
|
cache:
|
||||||
paths:
|
paths:
|
||||||
- .pip-cache/
|
|
||||||
- .uv-cache/
|
- .uv-cache/
|
||||||
- .venv/
|
|
||||||
|
|
||||||
before_script:
|
.uv_setup: &uv_setup
|
||||||
- curl -LsSf https://astral.sh/uv/install.sh | sh
|
- curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
- export PATH="$HOME/.cargo/bin:$PATH"
|
- export PATH="$HOME/.cargo/bin:$PATH"
|
||||||
- uv venv .venv
|
|
||||||
- source .venv/bin/activate
|
|
||||||
- uv pip install -r requirements.txt
|
|
||||||
|
|
||||||
lint:
|
|
||||||
stage: lint
|
|
||||||
script:
|
|
||||||
- uv pip install ruff
|
|
||||||
- ruff check .
|
|
||||||
- ruff format --check .
|
|
||||||
|
|
||||||
test:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- uv pip install pytest pytest-cov
|
|
||||||
- pytest --cov=./ --cov-report=xml
|
|
||||||
artifacts:
|
|
||||||
reports:
|
|
||||||
coverage_report:
|
|
||||||
coverage_format: cobertura
|
|
||||||
path: coverage.xml
|
|
||||||
|
|
||||||
dependency-check:
|
|
||||||
stage: lint
|
|
||||||
script:
|
|
||||||
- uv pip install pip-audit
|
|
||||||
- pip-audit
|
|
||||||
allow_failure: true
|
|
||||||
|
|
||||||
workflow:
|
workflow:
|
||||||
rules:
|
rules:
|
||||||
@@ -54,13 +23,110 @@ workflow:
|
|||||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
- if: $CI_COMMIT_TAG
|
- if: $CI_COMMIT_TAG
|
||||||
|
|
||||||
build:
|
lint:
|
||||||
stage: build
|
stage: lint
|
||||||
|
before_script:
|
||||||
|
- *uv_setup
|
||||||
script:
|
script:
|
||||||
- uv pip install build
|
- uv sync
|
||||||
- python -m build
|
- uv run ruff check .
|
||||||
|
- uv run ruff format --check .
|
||||||
|
|
||||||
|
test:
|
||||||
|
stage: test
|
||||||
|
before_script:
|
||||||
|
- *uv_setup
|
||||||
|
script:
|
||||||
|
- uv sync
|
||||||
|
- cd transform/sqlmesh_materia && uv run sqlmesh test
|
||||||
|
|
||||||
|
build:extract:
|
||||||
|
stage: build
|
||||||
|
before_script:
|
||||||
|
- *uv_setup
|
||||||
|
script:
|
||||||
|
- uv sync
|
||||||
|
- mkdir -p dist
|
||||||
|
- uv build --package psdonline --out-dir dist/extract
|
||||||
|
- cd dist/extract && tar -czf ../materia-extract-latest.tar.gz .
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- dist/
|
- dist/materia-extract-latest.tar.gz
|
||||||
|
expire_in: 1 week
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_TAG
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
|
||||||
|
build:transform:
|
||||||
|
stage: build
|
||||||
|
before_script:
|
||||||
|
- *uv_setup
|
||||||
|
script:
|
||||||
|
- uv sync
|
||||||
|
- mkdir -p dist
|
||||||
|
- uv build --package sqlmesh_materia --out-dir dist/transform
|
||||||
|
- cd dist/transform && tar -czf ../materia-transform-latest.tar.gz .
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- dist/materia-transform-latest.tar.gz
|
||||||
|
expire_in: 1 week
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
|
||||||
|
build:cli:
|
||||||
|
stage: build
|
||||||
|
before_script:
|
||||||
|
- *uv_setup
|
||||||
|
script:
|
||||||
|
- uv sync
|
||||||
|
- mkdir -p dist
|
||||||
|
- uv build --out-dir dist/cli
|
||||||
|
- cd dist/cli && tar -czf ../materia-cli-latest.tar.gz .
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- dist/materia-cli-latest.tar.gz
|
||||||
|
expire_in: 1 week
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
|
||||||
|
deploy:r2:
|
||||||
|
stage: deploy
|
||||||
|
image: rclone/rclone:latest
|
||||||
|
before_script:
|
||||||
|
- apk add --no-cache curl unzip
|
||||||
|
- curl -fsSL https://get.pulumi.com/esc/install.sh | sh
|
||||||
|
- export PATH="$HOME/.pulumi/bin:$PATH"
|
||||||
|
- esc login --token ${PULUMI_ACCESS_TOKEN}
|
||||||
|
- eval $(esc env open prod --format shell)
|
||||||
|
- |
|
||||||
|
mkdir -p ~/.config/rclone
|
||||||
|
cat > ~/.config/rclone/rclone.conf <<EOF
|
||||||
|
[r2]
|
||||||
|
type = s3
|
||||||
|
provider = Cloudflare
|
||||||
|
access_key_id = ${R2_ACCESS_KEY_ID}
|
||||||
|
secret_access_key = ${R2_SECRET_ACCESS_KEY}
|
||||||
|
endpoint = https://${R2_ENDPOINT}
|
||||||
|
acl = private
|
||||||
|
EOF
|
||||||
|
script:
|
||||||
|
- rclone copy dist/*.tar.gz r2:${R2_ARTIFACTS_BUCKET}/ -v
|
||||||
|
dependencies:
|
||||||
|
- build:extract
|
||||||
|
- build:transform
|
||||||
|
- build:cli
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
|
||||||
|
deploy:infra:
|
||||||
|
stage: deploy
|
||||||
|
image: pulumi/pulumi:latest
|
||||||
|
before_script:
|
||||||
|
- pulumi login --token ${PULUMI_ACCESS_TOKEN}
|
||||||
|
script:
|
||||||
|
- cd infra
|
||||||
|
- pulumi stack select prod
|
||||||
|
- pulumi up --yes
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
changes:
|
||||||
|
- infra/**/*
|
||||||
|
|||||||
@@ -200,4 +200,4 @@ GitLab CI runs three stages (`.gitlab-ci.yml`):
|
|||||||
- **Prod database:** `materia_prod.db` (not yet created)
|
- **Prod database:** `materia_prod.db` (not yet created)
|
||||||
|
|
||||||
Note: The dev database is large and should not be committed to git (.gitignore already configured).
|
Note: The dev database is large and should not be committed to git (.gitignore already configured).
|
||||||
- Can you memorize what we discussed
|
- We use a monorepo with uv workspaces
|
||||||
|
|||||||
933
MARKETOVERVIEW.md
Normal file
933
MARKETOVERVIEW.md
Normal file
@@ -0,0 +1,933 @@
|
|||||||
|
I need a market overview over the commodity analytics market. I need to know everything i need to build a software like kpler myself. data sources, partners etc. The report should have details on all companies like kpler that are used by commodity traders. The structure for each company part should be modeled after the parts of a business model canvas and have details on every part, especially where they probably get their data from. Also try finding out pricing or estimate it, if you estimate mark it as an estimate though. I have the below initial analyses, you can use that as input.
|
||||||
|
|
||||||
|
--
|
||||||
|
# Commodity Analytics Market: Comprehensive Overview
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
The commodity analytics market is dominated by platforms that provide real-time trade intelligence, cargo tracking, and market fundamentals. The market size is estimated at several billion dollars, with leading players including Kpler, Vortexa, S&P Global Platts, Bloomberg, and LSEG (formerly Refinitiv).
|
||||||
|
|
||||||
|
**Key Success Factors:**
|
||||||
|
- Access to multiple proprietary data sources (AIS, satellite, bills of lading, port data, refinery data)
|
||||||
|
- Real-time data processing and AI/ML capabilities
|
||||||
|
- Expert analyst teams providing market insights
|
||||||
|
- API and integration capabilities
|
||||||
|
- Global coverage across 40+ commodities
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Market Size & Growth
|
||||||
|
|
||||||
|
- **Kpler**: $40M ARR in 2022, targeting $300M+ ARR by 2026, serving 460+ clients with 4,500+ active users
|
||||||
|
- **Vortexa**: ~$95M annual revenue (2024 estimate), 30% YoY growth in ARR (2023)
|
||||||
|
- **Total addressable market**: Estimated $5-10B+ annually including all data providers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Major Players Analysis (Business Model Canvas Structure)
|
||||||
|
|
||||||
|
## 1. KPLER
|
||||||
|
|
||||||
|
### Customer Segments
|
||||||
|
- Physical commodity traders (oil, gas, LNG, dry bulk)
|
||||||
|
- Financial traders and hedge funds (BP, Shell, ExxonMobil, Goldman Sachs)
|
||||||
|
- Charterers and vessel operators
|
||||||
|
- Fleet managers
|
||||||
|
- Logistics and supply chain professionals
|
||||||
|
- Energy ministries and government agencies
|
||||||
|
|
||||||
|
### Value Propositions
|
||||||
|
- **"One platform for global trade intelligence"**
|
||||||
|
- Real-time cargo tracking across 40+ commodities
|
||||||
|
- 500M+ daily data points from 500+ sources
|
||||||
|
- Proprietary AIS network (largest in the world with 13,000+ receivers)
|
||||||
|
- AI-powered predictive models for destination forecasting
|
||||||
|
- Supply & demand balance modeling
|
||||||
|
- Tank-level inventory tracking
|
||||||
|
- 900+ refinery monitoring globally
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
- Direct sales team (offices in NY, Houston, London, Paris, Brussels, Dubai, Singapore, Tokyo, Cape Town)
|
||||||
|
- Web-based terminal
|
||||||
|
- API/SDK integrations
|
||||||
|
- Excel Add-in
|
||||||
|
- Snowflake Data Share
|
||||||
|
- LiveDB for pre-formatted analytics
|
||||||
|
|
||||||
|
### Customer Relationships
|
||||||
|
- Subscription-based model with recurring revenue
|
||||||
|
- Dedicated account management
|
||||||
|
- 24/7 support
|
||||||
|
- Training and onboarding
|
||||||
|
- Custom reports and consulting services
|
||||||
|
|
||||||
|
### Revenue Streams
|
||||||
|
- **Primary**: Subscription fees (tiered pricing by functionality and commodities)
|
||||||
|
- **Secondary**: Custom consulting and reports
|
||||||
|
- **Estimated Pricing**: $30,000-$100,000+ per user/year depending on commodity coverage and features (ESTIMATE)
|
||||||
|
|
||||||
|
### Key Resources
|
||||||
|
- **Technology**: Proprietary platform processing 10M+ data points daily
|
||||||
|
- **Data Assets**:
|
||||||
|
- Largest AIS network globally (owns MarineTraffic, FleetMon, Spire Maritime - acquired 2023-2024 for ~$474M total)
|
||||||
|
- 13,000+ AIS receivers (terrestrial + satellite)
|
||||||
|
- Historical data back to 2010
|
||||||
|
- **Human Capital**: 600+ employees, 35+ nationalities
|
||||||
|
- **Intellectual Property**: AI/ML algorithms, predictive models
|
||||||
|
|
||||||
|
### Key Activities
|
||||||
|
- Real-time data collection and processing
|
||||||
|
- AIS signal capture and interpretation
|
||||||
|
- Satellite imagery analysis
|
||||||
|
- Machine learning model development
|
||||||
|
- Analyst-driven market research
|
||||||
|
- Product development and platform maintenance
|
||||||
|
|
||||||
|
### Key Partnerships
|
||||||
|
- **Data Providers**:
|
||||||
|
- ClipperData (acquired 2021)
|
||||||
|
- JBC Energy (acquired 2022)
|
||||||
|
- COR-e power market data (acquired 2022)
|
||||||
|
- MarineTraffic (acquired 2023)
|
||||||
|
- FleetMon (acquired 2023)
|
||||||
|
- Spire Maritime satellite AIS (acquired 2024)
|
||||||
|
- Bills of lading providers (multiple sources)
|
||||||
|
- Port authorities (global)
|
||||||
|
- Customs agencies
|
||||||
|
- Baltic Exchange (freight pricing)
|
||||||
|
- Spark pricing data
|
||||||
|
- **Technology Partners**: Cloud infrastructure providers, satellite operators
|
||||||
|
|
||||||
|
### Cost Structure
|
||||||
|
- **Data Acquisition**: Bills of lading, satellite data, port data subscriptions
|
||||||
|
- **Infrastructure**: Cloud computing, data processing, storage
|
||||||
|
- **Personnel**: 600+ employees (engineers, data scientists, analysts, sales)
|
||||||
|
- **Technology Development**: Platform development, AI/ML
|
||||||
|
- **Acquisitions**: $474M+ spent on acquisitions (2021-2024)
|
||||||
|
- **Operations**: Global offices, customer support
|
||||||
|
|
||||||
|
### Data Sources (Critical for Building Similar Platform)
|
||||||
|
1. **AIS Data**: Own network + satellite constellation
|
||||||
|
2. **Bills of Lading**: Customs data from multiple countries
|
||||||
|
3. **Satellite Imagery**: Optical and SAR for tank monitoring
|
||||||
|
4. **Port Data**: Terminal and berth information
|
||||||
|
5. **Refinery Data**: Operating status, capacity, turnarounds
|
||||||
|
6. **Vessel Databases**: IMO registries, vessel specifications
|
||||||
|
7. **Freight Pricing**: Baltic Exchange partnership
|
||||||
|
8. **Market Data**: Exchange prices, broker quotes
|
||||||
|
9. **Official Statistics**: EIA, IEA, national agencies
|
||||||
|
10. **Weather Data**: For voyage predictions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. VORTEXA
|
||||||
|
|
||||||
|
### Customer Segments
|
||||||
|
- Energy traders (oil & gas)
|
||||||
|
- Shipping companies and freight brokers
|
||||||
|
- Financial institutions trading energy derivatives
|
||||||
|
- Oil majors and trading houses
|
||||||
|
- Refineries
|
||||||
|
- Energy analysts
|
||||||
|
|
||||||
|
### Value Propositions
|
||||||
|
- **"99% accuracy in tracking global energy flows"**
|
||||||
|
- Tracks $3.4 trillion in waterborne energy trades annually
|
||||||
|
- Real-time cargo tracking for oil, refined products, LNG, LPG
|
||||||
|
- Anywhere Freight Pricing: port-to-port pricing for 70M+ possible routes
|
||||||
|
- External data source transparency (BoL, fixtures, port data visibility)
|
||||||
|
- 4x per week Cushing inventory updates
|
||||||
|
- AI-powered predictive analytics
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
- Direct sales (offices in London, Geneva, Singapore, Houston, NYC, UAE)
|
||||||
|
- Web platform
|
||||||
|
- RESTful API
|
||||||
|
- Excel Add-in
|
||||||
|
- Python SDK
|
||||||
|
|
||||||
|
### Customer Relationships
|
||||||
|
- Subscription model
|
||||||
|
- Solutions architects for custom integrations
|
||||||
|
- Account management
|
||||||
|
- Analyst access for market insights
|
||||||
|
|
||||||
|
### Revenue Streams
|
||||||
|
- **Primary**: Subscription fees
|
||||||
|
- **Estimated Pricing**: $40,000-$120,000+ per user/year (ESTIMATE based on competitor analysis)
|
||||||
|
- 30% YoY ARR growth reported in 2023
|
||||||
|
|
||||||
|
### Key Resources
|
||||||
|
- **Data Network**: 1,000+ satellites, 500B+ AIS pings, 100+ market data sources
|
||||||
|
- **Technology**: Proprietary ML algorithms, deep tech
|
||||||
|
- **Human Capital**: 170+ employees across energy expertise, data science, engineering
|
||||||
|
- **Partnerships**: Baltic Exchange (freight pricing)
|
||||||
|
|
||||||
|
### Key Activities
|
||||||
|
- Real-time data aggregation from satellites and AIS
|
||||||
|
- Machine learning for cargo and grade prediction
|
||||||
|
- Freight pricing model development
|
||||||
|
- Market analysis and research
|
||||||
|
- Platform development
|
||||||
|
|
||||||
|
### Key Partnerships
|
||||||
|
- **Data Sources**:
|
||||||
|
- Baltic Exchange (freight pricing benchmarks)
|
||||||
|
- Multiple satellite providers
|
||||||
|
- AIS network providers
|
||||||
|
- Bills of lading vendors (multiple)
|
||||||
|
- Port authorities
|
||||||
|
- Fixtures databases
|
||||||
|
- Energy Aspects (strategic partnership for research)
|
||||||
|
- **Technology**: Cloud providers, satellite operators
|
||||||
|
|
||||||
|
### Cost Structure
|
||||||
|
- Satellite data acquisition
|
||||||
|
- AIS data feeds
|
||||||
|
- Cloud infrastructure for data processing
|
||||||
|
- 170+ employee salaries
|
||||||
|
- R&D for ML/AI development
|
||||||
|
- Global office operations
|
||||||
|
|
||||||
|
### Data Sources
|
||||||
|
1. **Satellite AIS**: Multiple providers
|
||||||
|
2. **Terrestrial AIS**: Partner networks
|
||||||
|
3. **Bills of Lading**: Commercial data providers
|
||||||
|
4. **Fixtures**: Shipping fixtures databases
|
||||||
|
5. **Port Data**: Terminal operations data
|
||||||
|
6. **Satellite Imagery**: For inventory monitoring
|
||||||
|
7. **Freight Pricing**: Baltic Exchange
|
||||||
|
8. **Market Prices**: Exchange and OTC data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. S&P GLOBAL COMMODITY INSIGHTS (PLATTS)
|
||||||
|
|
||||||
|
### Customer Segments
|
||||||
|
- Commodity traders (all asset classes)
|
||||||
|
- Producers and refiners
|
||||||
|
- Financial institutions
|
||||||
|
- Government agencies
|
||||||
|
- Risk managers
|
||||||
|
- Logistics companies
|
||||||
|
|
||||||
|
### Value Propositions
|
||||||
|
- **"Over 100 years of price transparency leadership"**
|
||||||
|
- Benchmark price assessments (industry standard)
|
||||||
|
- Coverage: oil, gas, power, petrochemicals, metals, agriculture, shipping
|
||||||
|
- Platts eWindow for real-time price discovery
|
||||||
|
- Forward curves and risk market data
|
||||||
|
- Largest editorial team (200+ commodity journalists in 20+ countries)
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
- Platts Connect platform
|
||||||
|
- Platts Dimensions Pro
|
||||||
|
- Data feeds (FTP, API)
|
||||||
|
- Integration with LSEG platforms
|
||||||
|
- Mobile apps
|
||||||
|
|
||||||
|
### Customer Relationships
|
||||||
|
- Enterprise-wide licensing (unlimited users)
|
||||||
|
- Fixed-fee model (no per-user charges)
|
||||||
|
- 24/7 support
|
||||||
|
- Dedicated account teams
|
||||||
|
- Methodology transparency sessions
|
||||||
|
|
||||||
|
### Revenue Streams
|
||||||
|
- **Primary**: Enterprise subscription packages
|
||||||
|
- Commodity-specific packages (Crude, Refined Products, Metals, Ag, Power, etc.)
|
||||||
|
- **Estimated Pricing**: $50,000-$500,000+ per enterprise annually depending on commodity coverage (ESTIMATE)
|
||||||
|
|
||||||
|
### Key Resources
|
||||||
|
- 200+ specialist commodity journalists
|
||||||
|
- Decades of historical price data (some back to 1900)
|
||||||
|
- Proprietary price assessment methodologies
|
||||||
|
- Editorial credibility and market trust
|
||||||
|
- TRADENET vessel tracking platform (acquired 2023)
|
||||||
|
|
||||||
|
### Key Activities
|
||||||
|
- Daily price assessments through market survey methodology
|
||||||
|
- Real-time price discovery via Platts eWindow
|
||||||
|
- Fundamental research and analysis
|
||||||
|
- Market intelligence gathering
|
||||||
|
- Editorial reporting
|
||||||
|
|
||||||
|
### Key Partnerships
|
||||||
|
- **Acquired Assets**:
|
||||||
|
- TRADENET (vessel tracking, 2023)
|
||||||
|
- Integration with parent company S&P Global Market Intelligence
|
||||||
|
- **Data Feeds**: Exchange data, OTC brokers, market participants
|
||||||
|
- **Technology**: Cloud providers
|
||||||
|
|
||||||
|
### Cost Structure
|
||||||
|
- 200+ editorial staff globally
|
||||||
|
- Technology infrastructure
|
||||||
|
- Data acquisition and validation
|
||||||
|
- Global office network
|
||||||
|
- Acquisitions (TRADENET, others)
|
||||||
|
|
||||||
|
### Data Sources
|
||||||
|
1. **Market Survey**: Direct from traders, producers, buyers
|
||||||
|
2. **Platts eWindow**: Real-time trading platform data
|
||||||
|
3. **Exchange Data**: CME, ICE, LME, etc.
|
||||||
|
4. **Vessel Tracking**: TRADENET acquisition
|
||||||
|
5. **Official Statistics**: Government agencies
|
||||||
|
6. **Broker Data**: OTC market data
|
||||||
|
7. **Fundamentals**: Supply/demand data from various sources
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. BLOOMBERG TERMINAL
|
||||||
|
|
||||||
|
### Customer Segments
|
||||||
|
- Financial professionals across all asset classes
|
||||||
|
- Commodity traders
|
||||||
|
- Portfolio managers
|
||||||
|
- Investment banks
|
||||||
|
- Hedge funds
|
||||||
|
- Corporate treasurers
|
||||||
|
|
||||||
|
### Value Propositions
|
||||||
|
- **"Most comprehensive financial data platform"**
|
||||||
|
- Real-time data across ALL asset classes
|
||||||
|
- 350,000+ Bloomberg Terminal users globally (network effects)
|
||||||
|
- Instant Bloomberg messaging (critical for trading community)
|
||||||
|
- Deep commodities coverage with pricing, fundamentals, analytics
|
||||||
|
- Integration of proprietary and third-party data
|
||||||
|
- Best-in-class charting and analytics
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
- Direct sales
|
||||||
|
- Proprietary Bloomberg keyboard and terminal
|
||||||
|
- Mobile apps (Bloomberg Anywhere)
|
||||||
|
- API services
|
||||||
|
|
||||||
|
### Customer Relationships
|
||||||
|
- Subscription with hardware/software bundle
|
||||||
|
- 24/7 support
|
||||||
|
- Training programs
|
||||||
|
- Bloomberg Intelligence research access
|
||||||
|
|
||||||
|
### Revenue Streams
|
||||||
|
- **Subscription**: $25,000-$30,000 per user per year
|
||||||
|
- Volume discounts for multiple terminals
|
||||||
|
- Data licensing for enterprise
|
||||||
|
|
||||||
|
### Key Resources
|
||||||
|
- 350,000+ user network creating liquidity in messaging/trading
|
||||||
|
- Bloomberg Intelligence (350+ research professionals)
|
||||||
|
- 2,700+ news journalists globally
|
||||||
|
- Decades of historical data
|
||||||
|
- Proprietary technology and infrastructure
|
||||||
|
|
||||||
|
### Key Activities
|
||||||
|
- Real-time data aggregation across all markets
|
||||||
|
- News gathering and distribution
|
||||||
|
- Research and analysis (Bloomberg Intelligence)
|
||||||
|
- Platform development
|
||||||
|
- Execution and trading facilitation
|
||||||
|
|
||||||
|
### Key Partnerships
|
||||||
|
- **Data Providers**:
|
||||||
|
- Exchange partnerships globally
|
||||||
|
- General Index (commodity pricing)
|
||||||
|
- Third-party data vendors
|
||||||
|
- Argus, Platts (pricing data available on terminal)
|
||||||
|
- **Trading Venues**: Execution partnerships
|
||||||
|
|
||||||
|
### Cost Structure
|
||||||
|
- 2,700+ journalists
|
||||||
|
- Technology infrastructure (massive scale)
|
||||||
|
- Data acquisition from thousands of sources
|
||||||
|
- Customer support
|
||||||
|
- R&D and platform development
|
||||||
|
|
||||||
|
### Data Sources
|
||||||
|
1. **Exchange Data**: Real-time from 300+ exchanges
|
||||||
|
2. **OTC Data**: Broker networks
|
||||||
|
3. **Commodity Prices**: Platts, Argus, General Index
|
||||||
|
4. **News**: Proprietary Bloomberg News
|
||||||
|
5. **Fundamentals**: Various data providers
|
||||||
|
6. **Reference Data**: Securities master database
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. LSEG (REFINITIV/EIKON)
|
||||||
|
|
||||||
|
### Customer Segments
|
||||||
|
- Financial traders (all asset classes)
|
||||||
|
- Commodity traders
|
||||||
|
- Risk managers
|
||||||
|
- Financial institutions
|
||||||
|
- Corporate clients
|
||||||
|
- Government agencies
|
||||||
|
|
||||||
|
### Value Propositions
|
||||||
|
- **"Workspace: Next-generation workflow solution"** (replacing Eikon by June 2025)
|
||||||
|
- Comprehensive commodities coverage
|
||||||
|
- Integration with Reuters News (exclusive access)
|
||||||
|
- Real-time and historical data
|
||||||
|
- Advanced analytics and charting
|
||||||
|
- Trading execution capabilities
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
- LSEG Workspace (replacing Eikon)
|
||||||
|
- Data APIs
|
||||||
|
- Excel Add-ins
|
||||||
|
- Mobile apps
|
||||||
|
- FTP data feeds
|
||||||
|
|
||||||
|
### Customer Relationships
|
||||||
|
- Subscription licensing
|
||||||
|
- Enterprise agreements
|
||||||
|
- 24/7 support
|
||||||
|
- Dedicated account management
|
||||||
|
|
||||||
|
### Revenue Streams
|
||||||
|
- **Subscription**: $800-$1,800 per user per month average (Eikon historical pricing)
|
||||||
|
- Enterprise data licenses
|
||||||
|
- API subscriptions
|
||||||
|
|
||||||
|
### Key Resources
|
||||||
|
- Reuters News exclusive access
|
||||||
|
- Decades of market data
|
||||||
|
- Strong relationships with exchanges
|
||||||
|
- Technology infrastructure
|
||||||
|
- IIR Energy (refinery data subsidiary)
|
||||||
|
- Wood Mackenzie partnership
|
||||||
|
|
||||||
|
### Key Activities
|
||||||
|
- Real-time data distribution
|
||||||
|
- News gathering (Reuters)
|
||||||
|
- Platform development
|
||||||
|
- Data normalization and cleaning
|
||||||
|
- Analytics development
|
||||||
|
|
||||||
|
### Key Partnerships
|
||||||
|
- **Data Providers**:
|
||||||
|
- IIR Energy (refinery outages, owned)
|
||||||
|
- Wood Mackenzie (fundamental data partnership)
|
||||||
|
- AIS providers for shipping data
|
||||||
|
- Exchange partnerships
|
||||||
|
- WBMS (World Bureau of Metal Statistics)
|
||||||
|
- **Technology**: Microsoft (strategic partnership for Workspace)
|
||||||
|
|
||||||
|
### Cost Structure
|
||||||
|
- Reuters news operations
|
||||||
|
- Technology infrastructure
|
||||||
|
- Data acquisition
|
||||||
|
- Customer support
|
||||||
|
- IIR Energy operations
|
||||||
|
|
||||||
|
### Data Sources
|
||||||
|
1. **Exchange Data**: Global coverage
|
||||||
|
2. **Reuters News**: Proprietary
|
||||||
|
3. **IIR Energy**: Refinery outages, capacity data
|
||||||
|
4. **Wood Mackenzie**: Fundamental data
|
||||||
|
5. **Vessel Tracking**: AIS providers
|
||||||
|
6. **Trade Data**: Various sources
|
||||||
|
7. **Fundamentals**: Research providers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. KAYRROS
|
||||||
|
|
||||||
|
### Customer Segments
|
||||||
|
- Financial institutions (trading firms, banks)
|
||||||
|
- Commodity traders
|
||||||
|
- Energy sector companies
|
||||||
|
- Carbon traders
|
||||||
|
- Government and regulatory bodies
|
||||||
|
|
||||||
|
### Value Propositions
|
||||||
|
- **"Environmental intelligence and asset observation"**
|
||||||
|
- AI and geoanalytics on satellite imagery
|
||||||
|
- Energy, climate, and sustainability insights
|
||||||
|
- Methane emissions monitoring
|
||||||
|
- Oil and gas infrastructure tracking
|
||||||
|
- Real-time market intelligence
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
- Direct platform access
|
||||||
|
- API integrations
|
||||||
|
- Custom dashboards
|
||||||
|
|
||||||
|
### Customer Relationships
|
||||||
|
- Subscription model
|
||||||
|
- Custom analytics projects
|
||||||
|
- Consulting services
|
||||||
|
|
||||||
|
### Revenue Streams
|
||||||
|
- Platform subscriptions
|
||||||
|
- Custom analytics projects
|
||||||
|
- **Estimated Pricing**: $50,000-$200,000+ per year (ESTIMATE based on enterprise focus)
|
||||||
|
|
||||||
|
### Key Resources
|
||||||
|
- Proprietary AI algorithms for satellite image analysis
|
||||||
|
- Team of satellite imagery specialists (largest in Europe)
|
||||||
|
- Relationships with satellite providers
|
||||||
|
- Environmental science expertise
|
||||||
|
|
||||||
|
### Key Activities
|
||||||
|
- Satellite imagery analysis using AI
|
||||||
|
- Environmental monitoring
|
||||||
|
- Asset tracking and production monitoring
|
||||||
|
- Algorithm development
|
||||||
|
- Custom research
|
||||||
|
|
||||||
|
### Key Partnerships
|
||||||
|
- **Satellite Providers**:
|
||||||
|
- Planet Labs (Stereo SkySat imagery partnership)
|
||||||
|
- Multiple satellite data providers
|
||||||
|
- **Technology**: AI/ML platforms, cloud infrastructure
|
||||||
|
|
||||||
|
### Cost Structure
|
||||||
|
- Satellite data acquisition
|
||||||
|
- AI/ML development team
|
||||||
|
- Data scientists and analysts
|
||||||
|
- Technology infrastructure
|
||||||
|
- Research and development
|
||||||
|
|
||||||
|
### Data Sources
|
||||||
|
1. **Satellite Imagery**: Planet Labs, Maxar, Airbus, others
|
||||||
|
2. **SAR Data**: Synthetic Aperture Radar satellites
|
||||||
|
3. **AIS Data**: Vessel tracking
|
||||||
|
4. **Official Statistics**: Government data
|
||||||
|
5. **Ground Truth Data**: Validation sources
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. OilX (Now Part of Energy Aspects)
|
||||||
|
|
||||||
|
### Customer Segments
|
||||||
|
- Oil traders
|
||||||
|
- Investment banks
|
||||||
|
- Hedge funds
|
||||||
|
- Oil majors
|
||||||
|
- Energy ministries (Middle East)
|
||||||
|
- Physical commodity traders
|
||||||
|
|
||||||
|
### Value Propositions
|
||||||
|
- **"The world's first digital oil analyst"**
|
||||||
|
- Real-time oil supply-demand balances (160+ countries)
|
||||||
|
- Daily oil production data by country and field
|
||||||
|
- Satellite-based storage monitoring (800+ oil fields)
|
||||||
|
- SAR and optical data for tank inventory
|
||||||
|
- Automated menial tasks (80% of analyst time saved)
|
||||||
|
- Nowcasting technology
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
- OilX Platform (web-based)
|
||||||
|
- API access
|
||||||
|
- Flat file delivery
|
||||||
|
- Excel integration
|
||||||
|
- Integration with Energy Aspects client portal
|
||||||
|
|
||||||
|
### Customer Relationships
|
||||||
|
- Subscription model
|
||||||
|
- Analyst support
|
||||||
|
- Direct access to research team
|
||||||
|
- Webinars and Q&A sessions
|
||||||
|
|
||||||
|
### Revenue Streams
|
||||||
|
- Platform subscriptions
|
||||||
|
- **Estimated Pricing**: $40,000-$100,000+ per user/year (ESTIMATE)
|
||||||
|
- Custom analytics
|
||||||
|
|
||||||
|
### Key Resources
|
||||||
|
- **Satellite Data Partnership**: European Space Agency (ESA)
|
||||||
|
- **Technology**: Proprietary ML algorithms, nowcasting models
|
||||||
|
- **Team**: Oil analysts, data scientists
|
||||||
|
- **Data**: Real-time from satellites, AIS, conventional sources
|
||||||
|
|
||||||
|
### Key Activities
|
||||||
|
- Satellite data processing (SAR and optical)
|
||||||
|
- Oil field monitoring and flaring detection
|
||||||
|
- Supply-demand modeling
|
||||||
|
- Nowcasting development
|
||||||
|
- Market analysis
|
||||||
|
|
||||||
|
### Key Partnerships
|
||||||
|
- **European Space Agency (ESA)**: Sentinel satellite data access
|
||||||
|
- **Aresys**: SAR processing technology
|
||||||
|
- **Signal Ocean**: Shipping intelligence
|
||||||
|
- **Energy Aspects**: Parent company (acquired 2023) providing research expertise
|
||||||
|
- **Funding**: GS Caltex, Citi ($2.2M seed funding)
|
||||||
|
|
||||||
|
### Cost Structure
|
||||||
|
- Satellite data from ESA and partners
|
||||||
|
- ML/AI development
|
||||||
|
- Analyst team
|
||||||
|
- Technology infrastructure
|
||||||
|
- Integration with Energy Aspects
|
||||||
|
|
||||||
|
### Data Sources
|
||||||
|
1. **Satellite Data**: ESA Sentinel (SAR), optical satellites
|
||||||
|
2. **AIS Data**: Vessel tracking via Signal Ocean
|
||||||
|
3. **Official Statistics**: Government agencies
|
||||||
|
4. **Field Data**: Production monitoring
|
||||||
|
5. **Refinery Data**: Operating status
|
||||||
|
6. **Conventional Oil Data**: Market statistics
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. WOOD MACKENZIE
|
||||||
|
|
||||||
|
### Customer Segments
|
||||||
|
- Energy companies
|
||||||
|
- Mining companies
|
||||||
|
- Financial institutions
|
||||||
|
- Government agencies
|
||||||
|
- Consultants
|
||||||
|
- Renewable energy developers
|
||||||
|
|
||||||
|
### Value Propositions
|
||||||
|
- **"Data and energy insight across entire supply chain"**
|
||||||
|
- Comprehensive coverage: oil, gas, power, renewables, metals, mining
|
||||||
|
- Real-time refinery monitoring (100+ refineries, 90% of US capacity)
|
||||||
|
- Pipeline flow monitoring (EMF technology)
|
||||||
|
- Long-term forecasting and scenario modeling
|
||||||
|
- 2,600+ market experts and data scientists
|
||||||
|
- Lens Direct API for seamless integration
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
- Wood Mackenzie Lens platform
|
||||||
|
- API services (Lens Direct)
|
||||||
|
- Custom reports and consulting
|
||||||
|
- Research portals
|
||||||
|
|
||||||
|
### Customer Relationships
|
||||||
|
- Subscription to research services
|
||||||
|
- Consulting engagements
|
||||||
|
- Direct analyst access
|
||||||
|
- Custom data solutions
|
||||||
|
|
||||||
|
### Revenue Streams
|
||||||
|
- Research subscriptions
|
||||||
|
- Consulting projects
|
||||||
|
- Data licensing
|
||||||
|
- **Estimated Pricing**: $30,000-$200,000+ per year depending on services (ESTIMATE)
|
||||||
|
|
||||||
|
### Key Resources
|
||||||
|
- 2,600+ analysts and experts globally
|
||||||
|
- Proprietary models for forecasting
|
||||||
|
- Decades of historical data
|
||||||
|
- Refinery monitoring technology (remote sensors)
|
||||||
|
- EMF technology for pipeline monitoring
|
||||||
|
- Strong relationships with energy companies
|
||||||
|
|
||||||
|
### Key Activities
|
||||||
|
- Market research and analysis
|
||||||
|
- Long-term forecasting
|
||||||
|
- Real-time monitoring (refineries, pipelines)
|
||||||
|
- Consulting services
|
||||||
|
- Data collection and modeling
|
||||||
|
|
||||||
|
### Key Partnerships
|
||||||
|
- **Integration Partners**:
|
||||||
|
- Thomson Reuters/LSEG (data available in Eikon/Workspace)
|
||||||
|
- Exchange and market data providers
|
||||||
|
- **Technology**: Remote sensing providers, satellite data
|
||||||
|
- **Clients**: Deep relationships with majors
|
||||||
|
|
||||||
|
### Cost Structure
|
||||||
|
- 2,600+ employee salaries
|
||||||
|
- Research and analysis operations
|
||||||
|
- Technology for monitoring (EMF sensors, satellite)
|
||||||
|
- Global office network
|
||||||
|
- Data acquisition
|
||||||
|
|
||||||
|
### Data Sources
|
||||||
|
1. **Refinery Monitoring**: Proprietary remote sensors (thermal, flaring detection)
|
||||||
|
2. **Pipeline Flows**: Electromagnetic field (EMF) technology
|
||||||
|
3. **Official Statistics**: Government agencies, regulatory bodies
|
||||||
|
4. **Company Data**: Direct from energy companies
|
||||||
|
5. **Satellite Data**: For asset monitoring
|
||||||
|
6. **Field Research**: Primary research from global team
|
||||||
|
7. **Exchange Data**: Price and trading data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. IIR ENERGY (Industrial Info Resources)
|
||||||
|
|
||||||
|
### Customer Segments
|
||||||
|
- Commodity traders (oil, gas, power)
|
||||||
|
- Physical traders
|
||||||
|
- Financial traders
|
||||||
|
- Refiners
|
||||||
|
- Risk managers
|
||||||
|
- Power generators
|
||||||
|
|
||||||
|
### Value Propositions
|
||||||
|
- **"Trusted data source for supply-side intelligence"**
|
||||||
|
- Real-time refinery outage monitoring (700+ global refineries)
|
||||||
|
- Power generation facility tracking
|
||||||
|
- Pipeline and terminal data
|
||||||
|
- Daily updates on planned and unplanned outages
|
||||||
|
- Primary research methodology
|
||||||
|
- Unit-by-unit capacity data
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
- Energy Live subscription platform
|
||||||
|
- API feeds
|
||||||
|
- Excel tools
|
||||||
|
- Custom reports
|
||||||
|
- Hotline for urgent intelligence
|
||||||
|
|
||||||
|
### Customer Relationships
|
||||||
|
- Subscription model
|
||||||
|
- Research hotline for verification
|
||||||
|
- Account management
|
||||||
|
- Custom research requests
|
||||||
|
|
||||||
|
### Revenue Streams
|
||||||
|
- Subscriptions to Energy Live platform
|
||||||
|
- **Estimated Pricing**: $20,000-$80,000+ per year (ESTIMATE)
|
||||||
|
- Custom reports
|
||||||
|
|
||||||
|
### Key Resources
|
||||||
|
- Global research staff (boots on the ground)
|
||||||
|
- PECWeb Database (project, event, capacity tracking)
|
||||||
|
- Decades of historical data
|
||||||
|
- Refinery Capacity Insights tool
|
||||||
|
- Primary research methodology
|
||||||
|
|
||||||
|
### Key Activities
|
||||||
|
- Daily monitoring of refinery outages
|
||||||
|
- Power plant tracking
|
||||||
|
- Primary research and verification
|
||||||
|
- Database maintenance
|
||||||
|
- Event tracking (weather, disasters)
|
||||||
|
|
||||||
|
### Key Partnerships
|
||||||
|
- **Owned by LSEG**: Data distributed through LSEG platforms
|
||||||
|
- **Integrated with**: Refinitiv/Eikon/Workspace
|
||||||
|
- **Data Sources**: Direct research, company contacts, regulatory filings
|
||||||
|
|
||||||
|
### Cost Structure
|
||||||
|
- Global research team
|
||||||
|
- Database infrastructure
|
||||||
|
- Customer support (hotline)
|
||||||
|
- Technology development
|
||||||
|
|
||||||
|
### Data Sources
|
||||||
|
1. **Primary Research**: Direct calls to refineries, power plants
|
||||||
|
2. **Company Communications**: Official announcements
|
||||||
|
3. **Regulatory Filings**: Government submissions
|
||||||
|
4. **Network**: Industry contacts globally
|
||||||
|
5. **Field Verification**: On-the-ground intelligence
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Essential Data Sources to Build Commodity Analytics Platform
|
||||||
|
|
||||||
|
## 1. AIS (Automatic Identification System) Data
|
||||||
|
**Primary Providers:**
|
||||||
|
- **Spire Maritime**: Satellite + terrestrial AIS, <1 minute latency
|
||||||
|
- **ORBCOMM**: Satellite AIS constellation
|
||||||
|
- **Pole Star**: AIS data provider
|
||||||
|
- **VesselFinder**: Lower-cost alternative
|
||||||
|
- **DIY Option**: Build own terrestrial receiver network (6,000+ stations needed for good coverage)
|
||||||
|
|
||||||
|
**Cost Estimate**: $100,000-$500,000+ per year for comprehensive satellite + terrestrial coverage
|
||||||
|
|
||||||
|
## 2. Satellite Imagery
|
||||||
|
**Optical Imagery Providers:**
|
||||||
|
- **Planet Labs**: Daily 3-5m resolution (PlanetScope), 50cm (SkySat)
|
||||||
|
- **Maxar Technologies**: 30cm resolution (WorldView Legion)
|
||||||
|
- **Airbus**: 30cm (Pléiades Neo)
|
||||||
|
|
||||||
|
**SAR (Synthetic Aperture Radar) Providers:**
|
||||||
|
- **ICEYE**: All-weather SAR microsatellites
|
||||||
|
- **Capella Space**: SAR data provider
|
||||||
|
- **European Space Agency**: Sentinel satellites (free but lower resolution)
|
||||||
|
|
||||||
|
**Cost Estimate**: $200,000-$1,000,000+ per year depending on coverage and resolution needs
|
||||||
|
|
||||||
|
## 3. Bills of Lading Data
|
||||||
|
**Providers:**
|
||||||
|
- **Panjiva** (S&P Global): 30+ countries, 10M+ businesses
|
||||||
|
- **ImportGenius**: US + 23 countries, detailed BoL data
|
||||||
|
- **Tendata**: 218+ countries
|
||||||
|
- **PIERS** (S&P Global): US + 14 countries
|
||||||
|
- **Descartes Datamyne**: US and Latin America
|
||||||
|
|
||||||
|
**Cost Estimate**: $30,000-$150,000+ per year depending on country coverage
|
||||||
|
|
||||||
|
## 4. Port Data
|
||||||
|
**Sources:**
|
||||||
|
- Direct partnerships with port authorities
|
||||||
|
- Port state control databases
|
||||||
|
- Terminal operators
|
||||||
|
- S&P Global Market Intelligence (port calls)
|
||||||
|
|
||||||
|
**Cost Estimate**: Variable, $50,000-$200,000+ for comprehensive coverage
|
||||||
|
|
||||||
|
## 5. Refinery Data
|
||||||
|
**Providers:**
|
||||||
|
- **IIR Energy**: Real-time outages, capacity (owned by LSEG)
|
||||||
|
- **Wood Mackenzie**: Remote monitoring technology
|
||||||
|
- **Primary Research**: Build own analyst network (expensive)
|
||||||
|
|
||||||
|
**Cost Estimate**: $50,000-$200,000+ per year
|
||||||
|
|
||||||
|
## 6. Freight Pricing Data
|
||||||
|
**Providers:**
|
||||||
|
- **Baltic Exchange**: Benchmark freight rates (licensing required)
|
||||||
|
- **Clarksons**: Shipping intelligence
|
||||||
|
- **Internal Models**: Build proprietary pricing algorithms (requires AIS + port data)
|
||||||
|
|
||||||
|
**Cost Estimate**: $50,000-$150,000+ per year
|
||||||
|
|
||||||
|
## 7. Exchange and Price Data
|
||||||
|
**Sources:**
|
||||||
|
- **CME Group**: Futures data
|
||||||
|
- **ICE**: Energy and commodity futures
|
||||||
|
- **LME**: Metals
|
||||||
|
- **Direct feeds**: From exchanges (expensive)
|
||||||
|
- **Aggregators**: S&P Global, LSEG, Bloomberg (redistributors)
|
||||||
|
|
||||||
|
**Cost Estimate**: $100,000-$500,000+ per year for comprehensive coverage
|
||||||
|
|
||||||
|
## 8. Official Statistics
|
||||||
|
**Sources:**
|
||||||
|
- **US EIA**: Energy Information Administration (free)
|
||||||
|
- **IEA**: International Energy Agency
|
||||||
|
- **National Statistics Agencies**: Most countries publish data
|
||||||
|
- **JODI**: Joint Oil Data Initiative
|
||||||
|
|
||||||
|
**Cost**: Mostly free, requires data aggregation infrastructure
|
||||||
|
|
||||||
|
## 9. Vessel Database
|
||||||
|
**Providers:**
|
||||||
|
- **IHS Markit Sea-web**: Comprehensive vessel database
|
||||||
|
- **Clarkson Research**: Shipping intelligence
|
||||||
|
- **Build from IMO**: International Maritime Organization registry
|
||||||
|
|
||||||
|
**Cost Estimate**: $30,000-$100,000+ per year
|
||||||
|
|
||||||
|
## 10. Weather Data
|
||||||
|
**Providers:**
|
||||||
|
- **IBM Weather**: Enterprise weather data
|
||||||
|
- **NOAA**: Free US government data
|
||||||
|
- **MeteoGroup**: European weather
|
||||||
|
- **AccuWeather**: Enterprise solutions
|
||||||
|
|
||||||
|
**Cost Estimate**: $20,000-$100,000+ per year
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Technology Stack Requirements
|
||||||
|
|
||||||
|
## Core Infrastructure
|
||||||
|
- **Cloud Platform**: AWS, Azure, or GCP ($100,000-$500,000+/year)
|
||||||
|
- **Data Processing**: Apache Spark, Kafka for streaming
|
||||||
|
- **Database**: PostgreSQL (time-series), MongoDB (documents), Redis (caching)
|
||||||
|
- **Data Lake**: S3, Azure Data Lake
|
||||||
|
|
||||||
|
## AI/ML Stack
|
||||||
|
- **Frameworks**: TensorFlow, PyTorch, scikit-learn
|
||||||
|
- **MLOps**: Kubeflow, MLflow
|
||||||
|
- **GPU Compute**: For satellite imagery processing
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
- **Web Framework**: React, Angular, or Vue.js
|
||||||
|
- **Mapping**: Mapbox, Google Maps API
|
||||||
|
- **Visualization**: D3.js, Plotly, Recharts
|
||||||
|
|
||||||
|
## APIs and Integration
|
||||||
|
- **REST APIs**: For data distribution
|
||||||
|
- **WebSockets**: Real-time data streaming
|
||||||
|
- **Excel Add-in**: COM Add-in for Excel integration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Estimated Total Startup Costs
|
||||||
|
|
||||||
|
## Minimum Viable Product (MVP)
|
||||||
|
- **Data Sources**: $500,000-$1,000,000/year
|
||||||
|
- **Technology Infrastructure**: $200,000-$500,000/year
|
||||||
|
- **Team (20-30 people)**:
|
||||||
|
- 5-8 Data Engineers: $120,000-$180,000 each
|
||||||
|
- 5-8 Software Engineers: $130,000-$200,000 each
|
||||||
|
- 3-5 Data Scientists: $140,000-$200,000 each
|
||||||
|
- 3-5 Commodity Analysts: $100,000-$150,000 each
|
||||||
|
- 2-3 Product Managers: $130,000-$180,000 each
|
||||||
|
- Sales and Support: 3-5 people
|
||||||
|
- **Total Team Cost**: $3,000,000-$5,000,000/year
|
||||||
|
|
||||||
|
**Year 1 Total**: $4,000,000-$7,000,000
|
||||||
|
**Years 2-3**: $6,000,000-$10,000,000/year to scale
|
||||||
|
|
||||||
|
## Full-Scale Competitor
|
||||||
|
- **Data Sources**: $2,000,000-$5,000,000/year
|
||||||
|
- **Technology**: $1,000,000-$3,000,000/year
|
||||||
|
- **Team (200-600 people)**: $30,000,000-$80,000,000/year
|
||||||
|
- **Acquisitions**: $100,000,000-$500,000,000 (as Kpler did)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Key Success Factors
|
||||||
|
|
||||||
|
1. **Data Accuracy**: Must match official statistics (EIA, customs data) with 95%+ accuracy
|
||||||
|
2. **Latency**: Real-time or near-real-time data (<5 minutes for AIS, intraday for inventory)
|
||||||
|
3. **Coverage**: Global reach across multiple commodities
|
||||||
|
4. **Analyst Expertise**: Commodity market experts to interpret data
|
||||||
|
5. **Network Effects**: More users = more valuable (especially for messaging/community features)
|
||||||
|
6. **API/Integration**: Seamless integration into customer workflows
|
||||||
|
7. **Trust**: Must build credibility in the market (takes years)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pricing Models in the Industry
|
||||||
|
|
||||||
|
## Subscription Tiers
|
||||||
|
- **Basic**: Single commodity, limited API access: $30,000-$50,000/user/year (ESTIMATE)
|
||||||
|
- **Professional**: Multiple commodities, full API: $60,000-$100,000/user/year (ESTIMATE)
|
||||||
|
- **Enterprise**: All commodities, unlimited users: $200,000-$1,000,000+/year (ESTIMATE)
|
||||||
|
|
||||||
|
## Enterprise Licensing
|
||||||
|
- Some providers (S&P Global Platts) offer unlimited user licensing at fixed fee
|
||||||
|
- More predictable for large organizations
|
||||||
|
|
||||||
|
## Data Feed Pricing
|
||||||
|
- Raw data feeds priced separately: $50,000-$500,000+/year
|
||||||
|
- API access often bundled or sold separately
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Competitive Landscape Summary
|
||||||
|
|
||||||
|
## Market Leaders (by segment)
|
||||||
|
- **Cargo Tracking & Maritime**: Kpler (market leader after acquisitions)
|
||||||
|
- **Energy Fundamentals**: Vortexa, OilX/Energy Aspects
|
||||||
|
- **Price Benchmarks**: S&P Global Platts (industry standard)
|
||||||
|
- **Comprehensive Financial Data**: Bloomberg Terminal
|
||||||
|
- **Multi-Asset Trading Platform**: LSEG Workspace
|
||||||
|
- **Environmental/Satellite Analytics**: Kayrros
|
||||||
|
- **Long-term Forecasting**: Wood Mackenzie
|
||||||
|
- **Refinery Intelligence**: IIR Energy, Wood Mackenzie
|
||||||
|
|
||||||
|
## Barriers to Entry
|
||||||
|
- **HIGH**: Data acquisition costs, need for scale
|
||||||
|
- **HIGH**: Building credibility and trust with traders
|
||||||
|
- **MEDIUM**: Technology development (can be built)
|
||||||
|
- **HIGH**: Sales cycle for enterprise software (12-18 months)
|
||||||
|
- **CRITICAL**: Access to proprietary data sources
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Recommendations for Building Similar Platform
|
||||||
|
|
||||||
|
1. **Start Narrow**: Focus on one commodity vertical (e.g., crude oil)
|
||||||
|
2. **Differentiate**: Find unique data source or methodology
|
||||||
|
3. **Build Core AIS**: Essential for cargo tracking - license or build network
|
||||||
|
4. **Partner for Data**: Don't try to acquire all data sources day 1
|
||||||
|
5. **Hire Experts**: Need commodity traders/analysts who understand the market
|
||||||
|
6. **API-First**: Make integration easy for quantitative traders
|
||||||
|
7. **Accuracy Over Speed**: In early days, prioritize accuracy to build trust
|
||||||
|
8. **Plan for Scale**: Will need significant capital ($20M+) to compete seriously
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This report is based on publicly available information and industry estimates. Actual pricing and specific capabilities may vary. Pricing marked as (ESTIMATE) is based on industry analysis and comparable products.*
|
||||||
7
beanflows_ssh
Normal file
7
beanflows_ssh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||||
|
QyNTUxOQAAACCfGESotAKXA3uc2Mu90jYfpbwqZyRF+VytareVIN3PkgAAAJjG2ri3xtq4
|
||||||
|
twAAAAtzc2gtZWQyNTUxOQAAACCfGESotAKXA3uc2Mu90jYfpbwqZyRF+VytareVIN3Pkg
|
||||||
|
AAAECiPTY1dlijk3nvQcqZckzW2RddBhlqRTp4CMqrqj4oLJ8YRKi0ApcDe5zYy73SNh+l
|
||||||
|
vCpnJEX5XK1qt5Ug3c+SAAAAD2RlZW1hbkBEZWVtYW5QQwECAwQFBg==
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
1
beanflows_ssh.pub
Normal file
1
beanflows_ssh.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ8YRKi0ApcDe5zYy73SNh+lvCpnJEX5XK1qt5Ug3c+S deeman@DeemanPC
|
||||||
@@ -10,8 +10,15 @@ requires-python = ">=3.13"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"pyarrow>=20.0.0",
|
"pyarrow>=20.0.0",
|
||||||
"python-dotenv>=1.1.0",
|
"python-dotenv>=1.1.0",
|
||||||
|
"typer>=0.15.0",
|
||||||
|
"hcloud>=2.3.0",
|
||||||
|
"paramiko>=3.5.0",
|
||||||
|
"pyyaml>=6.0.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
materia = "materia.cli:app"
|
||||||
|
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
exploration = [
|
exploration = [
|
||||||
|
|||||||
159
src/materia/cli.py
Normal file
159
src/materia/cli.py
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
"""Materia CLI - Management interface for BeanFlows.coffee infrastructure."""
|
||||||
|
|
||||||
|
import typer
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
app = typer.Typer(
|
||||||
|
name="materia",
|
||||||
|
help="BeanFlows.coffee data platform management CLI",
|
||||||
|
no_args_is_help=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def version():
|
||||||
|
"""Show Materia version."""
|
||||||
|
typer.echo("Materia CLI v0.1.0")
|
||||||
|
|
||||||
|
|
||||||
|
worker_app = typer.Typer(help="Manage worker instances")
|
||||||
|
app.add_typer(worker_app, name="worker")
|
||||||
|
|
||||||
|
|
||||||
|
@worker_app.command("list")
|
||||||
|
def worker_list(
|
||||||
|
provider: Annotated[str, typer.Option("--provider", "-p")] = "hetzner",
|
||||||
|
):
|
||||||
|
"""List all active worker instances."""
|
||||||
|
from materia.workers import list_workers
|
||||||
|
|
||||||
|
workers = list_workers(provider)
|
||||||
|
if not workers:
|
||||||
|
typer.echo("No active workers")
|
||||||
|
return
|
||||||
|
|
||||||
|
typer.echo(f"{'NAME':<30} {'IP':<15} {'TYPE':<10} {'STATUS':<10}")
|
||||||
|
typer.echo("-" * 70)
|
||||||
|
for worker in workers:
|
||||||
|
typer.echo(f"{worker.name:<30} {worker.ip:<15} {worker.type:<10} {worker.status:<10}")
|
||||||
|
|
||||||
|
|
||||||
|
@worker_app.command("create")
|
||||||
|
def worker_create(
|
||||||
|
name: Annotated[str, typer.Argument(help="Worker name")],
|
||||||
|
server_type: Annotated[str, typer.Option("--type", "-t")] = "ccx22",
|
||||||
|
provider: Annotated[str, typer.Option("--provider", "-p")] = "hetzner",
|
||||||
|
location: Annotated[str | None, typer.Option("--location", "-l")] = None,
|
||||||
|
):
|
||||||
|
"""Create a new worker instance."""
|
||||||
|
from materia.workers import create_worker
|
||||||
|
|
||||||
|
typer.echo(f"Creating worker '{name}' ({server_type}) on {provider}...")
|
||||||
|
worker = create_worker(name, server_type, provider, location)
|
||||||
|
typer.echo(f"✓ Worker created: {worker.ip}")
|
||||||
|
|
||||||
|
|
||||||
|
@worker_app.command("destroy")
|
||||||
|
def worker_destroy(
|
||||||
|
name: Annotated[str, typer.Argument(help="Worker name")],
|
||||||
|
provider: Annotated[str, typer.Option("--provider", "-p")] = "hetzner",
|
||||||
|
force: Annotated[bool, typer.Option("--force", "-f")] = False,
|
||||||
|
):
|
||||||
|
"""Destroy a worker instance."""
|
||||||
|
from materia.workers import destroy_worker
|
||||||
|
|
||||||
|
if not force:
|
||||||
|
confirm = typer.confirm(f"Destroy worker '{name}'?")
|
||||||
|
if not confirm:
|
||||||
|
raise typer.Abort()
|
||||||
|
|
||||||
|
typer.echo(f"Destroying worker '{name}'...")
|
||||||
|
destroy_worker(name, provider)
|
||||||
|
typer.echo("✓ Worker destroyed")
|
||||||
|
|
||||||
|
|
||||||
|
pipeline_app = typer.Typer(help="Execute data pipelines")
|
||||||
|
app.add_typer(pipeline_app, name="pipeline")
|
||||||
|
|
||||||
|
|
||||||
|
@pipeline_app.command("run")
|
||||||
|
def pipeline_run(
|
||||||
|
name: Annotated[str, typer.Argument(help="Pipeline name (extract, transform)")],
|
||||||
|
worker_type: Annotated[str | None, typer.Option("--worker", "-w")] = None,
|
||||||
|
provider: Annotated[str, typer.Option("--provider", "-p")] = "hetzner",
|
||||||
|
keep: Annotated[bool, typer.Option("--keep", help="Keep worker after completion")] = False,
|
||||||
|
):
|
||||||
|
"""Run a pipeline on an ephemeral worker."""
|
||||||
|
from materia.pipelines import run_pipeline
|
||||||
|
|
||||||
|
typer.echo(f"Running pipeline '{name}'...")
|
||||||
|
result = run_pipeline(name, worker_type, auto_destroy=not keep, provider=provider)
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
typer.echo(result.output)
|
||||||
|
typer.echo(f"\n✓ Pipeline completed successfully")
|
||||||
|
else:
|
||||||
|
typer.echo(result.error, err=True)
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@pipeline_app.command("list")
|
||||||
|
def pipeline_list():
|
||||||
|
"""List available pipelines."""
|
||||||
|
from materia.pipelines import PIPELINES
|
||||||
|
|
||||||
|
typer.echo("Available pipelines:")
|
||||||
|
for name, config in PIPELINES.items():
|
||||||
|
typer.echo(f" • {name:<15} (worker: {config.worker_type}, artifact: {config.artifact})")
|
||||||
|
|
||||||
|
|
||||||
|
secrets_app = typer.Typer(help="Manage secrets via Pulumi ESC")
|
||||||
|
app.add_typer(secrets_app, name="secrets")
|
||||||
|
|
||||||
|
|
||||||
|
@secrets_app.command("list")
|
||||||
|
def secrets_list():
|
||||||
|
"""List available secrets (keys only)."""
|
||||||
|
from materia.secrets import list_secrets
|
||||||
|
|
||||||
|
secrets = list_secrets()
|
||||||
|
if not secrets:
|
||||||
|
typer.echo("No secrets configured")
|
||||||
|
return
|
||||||
|
|
||||||
|
typer.echo("Available secrets:")
|
||||||
|
for key in secrets:
|
||||||
|
typer.echo(f" • {key}")
|
||||||
|
|
||||||
|
|
||||||
|
@secrets_app.command("get")
|
||||||
|
def secrets_get(
|
||||||
|
key: Annotated[str, typer.Argument(help="Secret key")],
|
||||||
|
):
|
||||||
|
"""Get a secret value."""
|
||||||
|
from materia.secrets import get_secret
|
||||||
|
|
||||||
|
value = get_secret(key)
|
||||||
|
if value is None:
|
||||||
|
typer.echo(f"Secret '{key}' not found", err=True)
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
typer.echo(value)
|
||||||
|
|
||||||
|
|
||||||
|
@secrets_app.command("test")
|
||||||
|
def secrets_test():
|
||||||
|
"""Test ESC connection and authentication."""
|
||||||
|
from materia.secrets import test_connection
|
||||||
|
|
||||||
|
typer.echo("Testing Pulumi ESC connection...")
|
||||||
|
if test_connection():
|
||||||
|
typer.echo("✓ ESC connection successful")
|
||||||
|
else:
|
||||||
|
typer.echo("✗ ESC connection failed", err=True)
|
||||||
|
typer.echo("\nMake sure you've run: esc login")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app()
|
||||||
139
src/materia/pipelines.py
Normal file
139
src/materia/pipelines.py
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
"""Pipeline execution on ephemeral workers."""
|
||||||
|
|
||||||
|
import paramiko
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from materia.workers import create_worker, destroy_worker
|
||||||
|
from materia.secrets import get_secret
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PipelineConfig:
|
||||||
|
worker_type: str
|
||||||
|
artifact: str
|
||||||
|
command: str
|
||||||
|
secrets: list[str]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PipelineResult:
|
||||||
|
success: bool
|
||||||
|
output: str
|
||||||
|
error: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
PIPELINES = {
|
||||||
|
"extract": PipelineConfig(
|
||||||
|
worker_type="ccx12",
|
||||||
|
artifact="materia-extract-latest.tar.gz",
|
||||||
|
command="./extract_psd",
|
||||||
|
secrets=["R2_ACCESS_KEY_ID", "R2_SECRET_ACCESS_KEY", "R2_ENDPOINT", "R2_ARTIFACTS_BUCKET"],
|
||||||
|
),
|
||||||
|
"transform": PipelineConfig(
|
||||||
|
worker_type="ccx22",
|
||||||
|
artifact="materia-transform-latest.tar.gz",
|
||||||
|
command="cd sqlmesh_materia && ./sqlmesh plan prod",
|
||||||
|
secrets=[
|
||||||
|
"CLOUDFLARE_API_TOKEN",
|
||||||
|
"ICEBERG_REST_URI",
|
||||||
|
"R2_WAREHOUSE_NAME",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _execute_ssh_command(ip: str, command: str, env_vars: dict[str, str]) -> tuple[str, str, int]:
|
||||||
|
ssh_key_path = get_secret("SSH_PRIVATE_KEY_PATH")
|
||||||
|
if not ssh_key_path:
|
||||||
|
raise ValueError("SSH_PRIVATE_KEY_PATH not found in secrets")
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
|
||||||
|
pkey = paramiko.RSAKey.from_private_key_file(ssh_key_path)
|
||||||
|
client.connect(ip, username="root", pkey=pkey)
|
||||||
|
|
||||||
|
env_string = " ".join([f"export {k}='{v}' &&" for k, v in env_vars.items()])
|
||||||
|
full_command = f"{env_string} {command}" if env_vars else command
|
||||||
|
|
||||||
|
stdin, stdout, stderr = client.exec_command(full_command)
|
||||||
|
exit_code = stdout.channel.recv_exit_status()
|
||||||
|
|
||||||
|
output = stdout.read().decode()
|
||||||
|
error = stderr.read().decode()
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
return output, error, exit_code
|
||||||
|
|
||||||
|
|
||||||
|
def run_pipeline(
|
||||||
|
pipeline_name: str,
|
||||||
|
worker_type: str | None = None,
|
||||||
|
auto_destroy: bool = True,
|
||||||
|
provider: str = "hetzner",
|
||||||
|
) -> PipelineResult:
|
||||||
|
if pipeline_name not in PIPELINES:
|
||||||
|
return PipelineResult(
|
||||||
|
success=False,
|
||||||
|
output="",
|
||||||
|
error=f"Unknown pipeline: {pipeline_name}. Available: {', '.join(PIPELINES.keys())}",
|
||||||
|
)
|
||||||
|
|
||||||
|
pipeline_config = PIPELINES[pipeline_name]
|
||||||
|
worker_type = worker_type or pipeline_config.worker_type
|
||||||
|
worker_name = f"materia-{pipeline_name}-worker"
|
||||||
|
|
||||||
|
r2_bucket = get_secret("R2_ARTIFACTS_BUCKET") or "materia-artifacts"
|
||||||
|
r2_endpoint = get_secret("R2_ENDPOINT")
|
||||||
|
|
||||||
|
if not r2_endpoint:
|
||||||
|
return PipelineResult(
|
||||||
|
success=False,
|
||||||
|
output="",
|
||||||
|
error="R2_ENDPOINT not configured in secrets",
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
worker = create_worker(worker_name, worker_type, provider)
|
||||||
|
|
||||||
|
artifact_url = f"https://{r2_endpoint}/{r2_bucket}/{pipeline_config.artifact}"
|
||||||
|
|
||||||
|
bootstrap_commands = [
|
||||||
|
f"curl -fsSL -o artifact.tar.gz {artifact_url}",
|
||||||
|
"tar -xzf artifact.tar.gz",
|
||||||
|
"chmod +x -R .",
|
||||||
|
]
|
||||||
|
|
||||||
|
for cmd in bootstrap_commands:
|
||||||
|
_, error, exit_code = _execute_ssh_command(worker.ip, cmd, {})
|
||||||
|
if exit_code != 0:
|
||||||
|
return PipelineResult(
|
||||||
|
success=False,
|
||||||
|
output="",
|
||||||
|
error=f"Bootstrap failed: {error}",
|
||||||
|
)
|
||||||
|
|
||||||
|
env_vars = {}
|
||||||
|
for secret_key in pipeline_config.secrets:
|
||||||
|
value = get_secret(secret_key)
|
||||||
|
if value:
|
||||||
|
env_vars[secret_key] = value
|
||||||
|
|
||||||
|
command = pipeline_config.command
|
||||||
|
output, error, exit_code = _execute_ssh_command(worker.ip, command, env_vars)
|
||||||
|
|
||||||
|
success = exit_code == 0
|
||||||
|
|
||||||
|
return PipelineResult(
|
||||||
|
success=success,
|
||||||
|
output=output,
|
||||||
|
error=error if not success else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if auto_destroy:
|
||||||
|
try:
|
||||||
|
destroy_worker(worker_name, provider)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
48
src/materia/providers/__init__.py
Normal file
48
src/materia/providers/__init__.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
"""Cloud provider abstraction for worker management."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Instance:
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
ip: str
|
||||||
|
status: str
|
||||||
|
provider: str
|
||||||
|
type: str
|
||||||
|
|
||||||
|
|
||||||
|
class ProviderModule(Protocol):
|
||||||
|
def create_instance(
|
||||||
|
name: str,
|
||||||
|
instance_type: str,
|
||||||
|
ssh_key: str,
|
||||||
|
location: str | None = None,
|
||||||
|
) -> Instance: ...
|
||||||
|
|
||||||
|
def destroy_instance(instance_id: str) -> None: ...
|
||||||
|
|
||||||
|
def list_instances(label: str | None = None) -> list[Instance]: ...
|
||||||
|
|
||||||
|
def get_instance(name: str) -> Instance | None: ...
|
||||||
|
|
||||||
|
def wait_for_ssh(ip: str, timeout: int = 300) -> bool: ...
|
||||||
|
|
||||||
|
|
||||||
|
def get_provider(provider_name: str) -> ProviderModule:
|
||||||
|
if provider_name == "hetzner":
|
||||||
|
from materia.providers import hetzner
|
||||||
|
return hetzner
|
||||||
|
elif provider_name == "ovh":
|
||||||
|
from materia.providers import ovh
|
||||||
|
return ovh
|
||||||
|
elif provider_name == "scaleway":
|
||||||
|
from materia.providers import scaleway
|
||||||
|
return scaleway
|
||||||
|
elif provider_name == "oracle":
|
||||||
|
from materia.providers import oracle
|
||||||
|
return oracle
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown provider: {provider_name}")
|
||||||
122
src/materia/providers/hetzner.py
Normal file
122
src/materia/providers/hetzner.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
"""Hetzner Cloud provider implementation."""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from hcloud import Client
|
||||||
|
from hcloud.images import Image
|
||||||
|
from hcloud.server_types import ServerType
|
||||||
|
|
||||||
|
from materia.providers import Instance
|
||||||
|
from materia.secrets import get_secret
|
||||||
|
|
||||||
|
|
||||||
|
def _get_client() -> Client:
|
||||||
|
token = get_secret("HETZNER_TOKEN")
|
||||||
|
if not token:
|
||||||
|
raise ValueError("HETZNER_TOKEN not found in secrets")
|
||||||
|
return Client(token=token)
|
||||||
|
|
||||||
|
|
||||||
|
def create_instance(
|
||||||
|
name: str,
|
||||||
|
instance_type: str,
|
||||||
|
ssh_key: str,
|
||||||
|
location: str | None = None,
|
||||||
|
) -> Instance:
|
||||||
|
client = _get_client()
|
||||||
|
|
||||||
|
# Get or create SSH key
|
||||||
|
ssh_keys = client.ssh_keys.get_all(name="materia-key")
|
||||||
|
if ssh_keys:
|
||||||
|
hcloud_key = ssh_keys[0]
|
||||||
|
else:
|
||||||
|
hcloud_key = client.ssh_keys.create(name="materia-key", public_key=ssh_key)
|
||||||
|
|
||||||
|
server_type = ServerType(name=instance_type)
|
||||||
|
image = Image(name="ubuntu-24.04")
|
||||||
|
location_obj = location or "nbg1"
|
||||||
|
|
||||||
|
response = client.servers.create(
|
||||||
|
name=name,
|
||||||
|
server_type=server_type,
|
||||||
|
image=image,
|
||||||
|
ssh_keys=[hcloud_key],
|
||||||
|
location=location_obj,
|
||||||
|
labels={"managed_by": "materia"},
|
||||||
|
)
|
||||||
|
|
||||||
|
server = response.server
|
||||||
|
server.wait_until_status_is("running")
|
||||||
|
|
||||||
|
return Instance(
|
||||||
|
id=str(server.id),
|
||||||
|
name=server.name,
|
||||||
|
ip=server.public_net.ipv4.ip,
|
||||||
|
status=server.status,
|
||||||
|
provider="hetzner",
|
||||||
|
type=instance_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def destroy_instance(instance_id: str) -> None:
|
||||||
|
client = _get_client()
|
||||||
|
server = client.servers.get_by_id(int(instance_id))
|
||||||
|
if server:
|
||||||
|
server.delete()
|
||||||
|
|
||||||
|
|
||||||
|
def list_instances(label: str | None = None) -> list[Instance]:
|
||||||
|
client = _get_client()
|
||||||
|
|
||||||
|
label_selector = {"managed_by": "materia"}
|
||||||
|
if label:
|
||||||
|
label_selector["pipeline"] = label
|
||||||
|
|
||||||
|
servers = client.servers.get_all(label_selector=label_selector)
|
||||||
|
|
||||||
|
return [
|
||||||
|
Instance(
|
||||||
|
id=str(server.id),
|
||||||
|
name=server.name,
|
||||||
|
ip=server.public_net.ipv4.ip,
|
||||||
|
status=server.status,
|
||||||
|
provider="hetzner",
|
||||||
|
type=server.server_type.name,
|
||||||
|
)
|
||||||
|
for server in servers
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_instance(name: str) -> Instance | None:
|
||||||
|
client = _get_client()
|
||||||
|
servers = client.servers.get_all(name=name)
|
||||||
|
if not servers:
|
||||||
|
return None
|
||||||
|
|
||||||
|
server = servers[0]
|
||||||
|
return Instance(
|
||||||
|
id=str(server.id),
|
||||||
|
name=server.name,
|
||||||
|
ip=server.public_net.ipv4.ip,
|
||||||
|
status=server.status,
|
||||||
|
provider="hetzner",
|
||||||
|
type=server.server_type.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_ssh(ip: str, timeout: int = 300) -> bool:
|
||||||
|
start = time.time()
|
||||||
|
while time.time() - start < timeout:
|
||||||
|
try:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(5)
|
||||||
|
result = sock.connect_ex((ip, 22))
|
||||||
|
sock.close()
|
||||||
|
if result == 0:
|
||||||
|
time.sleep(10)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
time.sleep(5)
|
||||||
|
return False
|
||||||
28
src/materia/providers/oracle.py
Normal file
28
src/materia/providers/oracle.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"""Oracle Cloud provider implementation."""
|
||||||
|
|
||||||
|
from materia.providers import Instance
|
||||||
|
|
||||||
|
|
||||||
|
def create_instance(
|
||||||
|
name: str,
|
||||||
|
instance_type: str,
|
||||||
|
ssh_key: str,
|
||||||
|
location: str | None = None,
|
||||||
|
) -> Instance:
|
||||||
|
raise NotImplementedError("Oracle Cloud provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def destroy_instance(instance_id: str) -> None:
|
||||||
|
raise NotImplementedError("Oracle Cloud provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def list_instances(label: str | None = None) -> list[Instance]:
|
||||||
|
raise NotImplementedError("Oracle Cloud provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def get_instance(name: str) -> Instance | None:
|
||||||
|
raise NotImplementedError("Oracle Cloud provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_ssh(ip: str, timeout: int = 300) -> bool:
|
||||||
|
raise NotImplementedError("Oracle Cloud provider not yet implemented")
|
||||||
28
src/materia/providers/ovh.py
Normal file
28
src/materia/providers/ovh.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"""OVH Cloud provider implementation."""
|
||||||
|
|
||||||
|
from materia.providers import Instance
|
||||||
|
|
||||||
|
|
||||||
|
def create_instance(
|
||||||
|
name: str,
|
||||||
|
instance_type: str,
|
||||||
|
ssh_key: str,
|
||||||
|
location: str | None = None,
|
||||||
|
) -> Instance:
|
||||||
|
raise NotImplementedError("OVH provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def destroy_instance(instance_id: str) -> None:
|
||||||
|
raise NotImplementedError("OVH provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def list_instances(label: str | None = None) -> list[Instance]:
|
||||||
|
raise NotImplementedError("OVH provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def get_instance(name: str) -> Instance | None:
|
||||||
|
raise NotImplementedError("OVH provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_ssh(ip: str, timeout: int = 300) -> bool:
|
||||||
|
raise NotImplementedError("OVH provider not yet implemented")
|
||||||
28
src/materia/providers/scaleway.py
Normal file
28
src/materia/providers/scaleway.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"""Scaleway provider implementation."""
|
||||||
|
|
||||||
|
from materia.providers import Instance
|
||||||
|
|
||||||
|
|
||||||
|
def create_instance(
|
||||||
|
name: str,
|
||||||
|
instance_type: str,
|
||||||
|
ssh_key: str,
|
||||||
|
location: str | None = None,
|
||||||
|
) -> Instance:
|
||||||
|
raise NotImplementedError("Scaleway provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def destroy_instance(instance_id: str) -> None:
|
||||||
|
raise NotImplementedError("Scaleway provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def list_instances(label: str | None = None) -> list[Instance]:
|
||||||
|
raise NotImplementedError("Scaleway provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def get_instance(name: str) -> Instance | None:
|
||||||
|
raise NotImplementedError("Scaleway provider not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_ssh(ip: str, timeout: int = 300) -> bool:
|
||||||
|
raise NotImplementedError("Scaleway provider not yet implemented")
|
||||||
44
src/materia/secrets.py
Normal file
44
src/materia/secrets.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"""Secrets management via Pulumi ESC."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def _load_environment() -> dict[str, str]:
|
||||||
|
"""Load secrets from Pulumi ESC environment."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["esc", "env", "open", "prod", "--format", "json"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
data = json.loads(result.stdout)
|
||||||
|
return data.get("values", {})
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
raise RuntimeError(f"Failed to load ESC environment: {e.stderr}")
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise RuntimeError("ESC CLI not found. Install with: curl -fsSL https://get.pulumi.com/esc/install.sh | sh")
|
||||||
|
|
||||||
|
|
||||||
|
def get_secret(key: str) -> str | None:
|
||||||
|
"""Get a secret value by key."""
|
||||||
|
env = _load_environment()
|
||||||
|
return env.get(key)
|
||||||
|
|
||||||
|
|
||||||
|
def list_secrets() -> list[str]:
|
||||||
|
"""List all available secret keys."""
|
||||||
|
env = _load_environment()
|
||||||
|
return list(env.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def test_connection() -> bool:
|
||||||
|
"""Test ESC connection."""
|
||||||
|
try:
|
||||||
|
_load_environment()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
44
src/materia/workers.py
Normal file
44
src/materia/workers.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"""Worker instance management."""
|
||||||
|
|
||||||
|
from materia.providers import Instance, get_provider
|
||||||
|
from materia.secrets import get_secret
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PROVIDER = "hetzner"
|
||||||
|
|
||||||
|
|
||||||
|
def list_workers(provider: str = DEFAULT_PROVIDER) -> list[Instance]:
|
||||||
|
p = get_provider(provider)
|
||||||
|
return p.list_instances()
|
||||||
|
|
||||||
|
|
||||||
|
def create_worker(
|
||||||
|
name: str,
|
||||||
|
server_type: str,
|
||||||
|
provider: str = DEFAULT_PROVIDER,
|
||||||
|
location: str | None = None,
|
||||||
|
) -> Instance:
|
||||||
|
ssh_key = get_secret("SSH_PUBLIC_KEY")
|
||||||
|
if not ssh_key:
|
||||||
|
raise ValueError("SSH_PUBLIC_KEY not found in secrets")
|
||||||
|
|
||||||
|
p = get_provider(provider)
|
||||||
|
instance = p.create_instance(name, server_type, ssh_key, location)
|
||||||
|
|
||||||
|
if not p.wait_for_ssh(instance.ip):
|
||||||
|
raise RuntimeError(f"SSH never became available on {instance.ip}")
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
def destroy_worker(name: str, provider: str = DEFAULT_PROVIDER) -> None:
|
||||||
|
p = get_provider(provider)
|
||||||
|
instance = p.get_instance(name)
|
||||||
|
if not instance:
|
||||||
|
raise ValueError(f"Worker '{name}' not found")
|
||||||
|
p.destroy_instance(instance.id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_worker(name: str, provider: str = DEFAULT_PROVIDER) -> Instance | None:
|
||||||
|
p = get_provider(provider)
|
||||||
|
return p.get_instance(name)
|
||||||
76
todo.md
Normal file
76
todo.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
|
||||||
|
● Based on the infrastructure we built, here are the exact tasks to set up secrets in
|
||||||
|
Pulumi ESC:
|
||||||
|
|
||||||
|
1. Install Pulumi ESC CLI
|
||||||
|
|
||||||
|
curl -fsSL https://get.pulumi.com/esc/install.sh | sh
|
||||||
|
export PATH="$HOME/.pulumi/bin:$PATH"
|
||||||
|
|
||||||
|
2. Login to Pulumi
|
||||||
|
|
||||||
|
esc login
|
||||||
|
|
||||||
|
This will open a browser for authentication. You'll get a PULUMI_ACCESS_TOKEN - save
|
||||||
|
this for GitLab CI.
|
||||||
|
|
||||||
|
3. Create Production Environment
|
||||||
|
|
||||||
|
esc env init <your-org>/prod
|
||||||
|
|
||||||
|
Replace <your-org> with your Pulumi organization name.
|
||||||
|
|
||||||
|
4. Set All Required Secrets
|
||||||
|
|
||||||
|
# SSH Keys
|
||||||
|
esc env set <your-org>/prod SSH_PUBLIC_KEY "ssh-rsa AAAA..."
|
||||||
|
esc env set <your-org>/prod SSH_PRIVATE_KEY_PATH "/path/to/private/key"
|
||||||
|
|
||||||
|
# Hetzner
|
||||||
|
esc env set <your-org>/prod HETZNER_API_TOKEN "your-hetzner-token"
|
||||||
|
|
||||||
|
# Cloudflare R2 (for artifact storage)
|
||||||
|
esc env set <your-org>/prod R2_ACCESS_KEY_ID "your-r2-access-key"
|
||||||
|
esc env set <your-org>/prod R2_SECRET_ACCESS_KEY "your-r2-secret-key"
|
||||||
|
esc env set <your-org>/prod R2_ENDPOINT "account-id.r2.cloudflarestorage.com"
|
||||||
|
esc env set <your-org>/prod R2_ARTIFACTS_BUCKET "materia-artifacts"
|
||||||
|
|
||||||
|
# Cloudflare R2 Data Catalog (for Iceberg)
|
||||||
|
esc env set <your-org>/prod CLOUDFLARE_API_TOKEN "your-cf-api-token"
|
||||||
|
esc env set <your-org>/prod ICEBERG_REST_URI "https://api.cloudflare.com/client/v4/acco
|
||||||
|
unts/YOUR_ACCOUNT_ID/r2/buckets/YOUR_WAREHOUSE_BUCKET/iceberg"
|
||||||
|
esc env set <your-org>/prod R2_WAREHOUSE_NAME "materia"
|
||||||
|
|
||||||
|
5. Verify Secrets
|
||||||
|
|
||||||
|
esc env open <your-org>/prod --format shell
|
||||||
|
|
||||||
|
This shows all secrets as environment variables. You should see all the keys listed
|
||||||
|
above.
|
||||||
|
|
||||||
|
6. Test Locally
|
||||||
|
|
||||||
|
eval $(esc env open <your-org>/prod --format shell)
|
||||||
|
materia secrets list
|
||||||
|
materia secrets test
|
||||||
|
|
||||||
|
7. Configure GitLab CI
|
||||||
|
|
||||||
|
In your GitLab project settings → CI/CD → Variables, add:
|
||||||
|
|
||||||
|
- Key: PULUMI_ACCESS_TOKEN
|
||||||
|
- Value: (the token from step 2)
|
||||||
|
- Protected: Yes
|
||||||
|
- Masked: Yes
|
||||||
|
|
||||||
|
That's it! The CI/CD pipeline and materia CLI will automatically pull all other secrets
|
||||||
|
from ESC.
|
||||||
|
|
||||||
|
Where to Get Each Secret
|
||||||
|
|
||||||
|
- SSH Keys: Generate with ssh-keygen -t rsa -b 4096
|
||||||
|
- Hetzner API Token: https://console.hetzner.cloud/ → Project → Security → API Tokens
|
||||||
|
- R2 Credentials: Cloudflare Dashboard → R2 → Manage R2 API Tokens
|
||||||
|
- Cloudflare API Token: Cloudflare Dashboard → My Profile → API Tokens (needs R2
|
||||||
|
permissions)
|
||||||
|
- Iceberg REST URI: Format shown above - get account ID from Cloudflare dashboard URL
|
||||||
@@ -15,16 +15,22 @@ gateways:
|
|||||||
|
|
||||||
prod:
|
prod:
|
||||||
connection:
|
connection:
|
||||||
# For more information on configuring the connection to your execution engine, visit:
|
|
||||||
# https://sqlmesh.readthedocs.io/en/stable/reference/configuration/#connection
|
|
||||||
# https://sqlmesh.readthedocs.io/en/stable/integrations/engines/duckdb/#connection-options
|
|
||||||
type: duckdb
|
type: duckdb
|
||||||
|
database: ':memory:'
|
||||||
database: materia_prod.db
|
|
||||||
extensions:
|
extensions:
|
||||||
- name: zipfs
|
|
||||||
- name: httpfs
|
- name: httpfs
|
||||||
- name: iceberg
|
- name: iceberg
|
||||||
|
init_script: |
|
||||||
|
CREATE SECRET IF NOT EXISTS r2_secret (
|
||||||
|
TYPE ICEBERG,
|
||||||
|
TOKEN '{{ env_var("CLOUDFLARE_API_TOKEN") }}'
|
||||||
|
);
|
||||||
|
ATTACH '{{ env_var("R2_WAREHOUSE_NAME", "materia") }}' AS catalog (
|
||||||
|
TYPE ICEBERG,
|
||||||
|
ENDPOINT '{{ env_var("ICEBERG_REST_URI") }}'
|
||||||
|
);
|
||||||
|
CREATE SCHEMA IF NOT EXISTS catalog.materia;
|
||||||
|
USE catalog.materia;
|
||||||
|
|
||||||
|
|
||||||
default_gateway: dev
|
default_gateway: dev
|
||||||
|
|||||||
284
uv.lock
generated
284
uv.lock
generated
@@ -1,6 +1,11 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 2
|
revision = 2
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version >= '3.14' and platform_python_implementation != 'PyPy'",
|
||||||
|
"python_full_version < '3.14' and platform_python_implementation != 'PyPy'",
|
||||||
|
"platform_python_implementation == 'PyPy'",
|
||||||
|
]
|
||||||
|
|
||||||
[manifest]
|
[manifest]
|
||||||
members = [
|
members = [
|
||||||
@@ -76,6 +81,72 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" },
|
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bcrypt"
|
||||||
|
version = "5.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806, upload-time = "2025-09-25T19:49:05.102Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626, upload-time = "2025-09-25T19:49:06.723Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853, upload-time = "2025-09-25T19:49:08.028Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793, upload-time = "2025-09-25T19:49:09.727Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930, upload-time = "2025-09-25T19:49:11.204Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194, upload-time = "2025-09-25T19:49:12.524Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381, upload-time = "2025-09-25T19:49:14.308Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750, upload-time = "2025-09-25T19:49:15.584Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757, upload-time = "2025-09-25T19:49:17.244Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740, upload-time = "2025-09-25T19:49:18.693Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197, upload-time = "2025-09-25T19:49:20.523Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974, upload-time = "2025-09-25T19:49:22.254Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498, upload-time = "2025-09-25T19:49:24.134Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853, upload-time = "2025-09-25T19:49:25.702Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626, upload-time = "2025-09-25T19:49:26.928Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload-time = "2025-09-25T19:49:28.365Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload-time = "2025-09-25T19:49:30.39Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload-time = "2025-09-25T19:49:32.144Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload-time = "2025-09-25T19:49:33.885Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload-time = "2025-09-25T19:49:35.144Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload-time = "2025-09-25T19:49:36.793Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload-time = "2025-09-25T19:49:38.164Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload-time = "2025-09-25T19:49:39.917Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload-time = "2025-09-25T19:49:41.227Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload-time = "2025-09-25T19:49:43.08Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload-time = "2025-09-25T19:49:44.971Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload-time = "2025-09-25T19:49:46.162Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload-time = "2025-09-25T19:49:47.345Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cattrs"
|
name = "cattrs"
|
||||||
version = "25.1.1"
|
version = "25.1.1"
|
||||||
@@ -100,24 +171,47 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cffi"
|
name = "cffi"
|
||||||
version = "1.17.1"
|
version = "2.0.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "pycparser" },
|
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" },
|
{ url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" },
|
{ url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" },
|
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" },
|
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" },
|
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" },
|
{ url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" },
|
{ url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" },
|
{ url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" },
|
{ url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" },
|
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" },
|
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -203,6 +297,62 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/07/4b/290b4c3efd6417a8b0c284896de19b1d5855e6dbdb97d2a35e68fa42de85/croniter-6.0.0-py2.py3-none-any.whl", hash = "sha256:2f878c3856f17896979b2a4379ba1f09c83e374931ea15cc835c5dd2eee9b368", size = 25468, upload-time = "2024-12-17T17:17:45.359Z" },
|
{ url = "https://files.pythonhosted.org/packages/07/4b/290b4c3efd6417a8b0c284896de19b1d5855e6dbdb97d2a35e68fa42de85/croniter-6.0.0-py2.py3-none-any.whl", hash = "sha256:2f878c3856f17896979b2a4379ba1f09c83e374931ea15cc835c5dd2eee9b368", size = 25468, upload-time = "2024-12-17T17:17:45.359Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cryptography"
|
||||||
|
version = "46.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/4a/9b/e301418629f7bfdf72db9e80ad6ed9d1b83c487c471803eaa6464c511a01/cryptography-46.0.2.tar.gz", hash = "sha256:21b6fc8c71a3f9a604f028a329e5560009cc4a3a828bfea5fcba8eb7647d88fe", size = 749293, upload-time = "2025-10-01T00:29:11.856Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/98/7a8df8c19a335c8028414738490fc3955c0cecbfdd37fcc1b9c3d04bd561/cryptography-46.0.2-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3e32ab7dd1b1ef67b9232c4cf5e2ee4cd517d4316ea910acaaa9c5712a1c663", size = 7261255, upload-time = "2025-10-01T00:27:22.947Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/38/b2adb2aa1baa6706adc3eb746691edd6f90a656a9a65c3509e274d15a2b8/cryptography-46.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1fd1a69086926b623ef8126b4c33d5399ce9e2f3fac07c9c734c2a4ec38b6d02", size = 4297596, upload-time = "2025-10-01T00:27:25.258Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/27/0f190ada240003119488ae66c897b5e97149292988f556aef4a6a2a57595/cryptography-46.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb7fb9cd44c2582aa5990cf61a4183e6f54eea3172e54963787ba47287edd135", size = 4450899, upload-time = "2025-10-01T00:27:27.458Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/d5/e4744105ab02fdf6bb58ba9a816e23b7a633255987310b4187d6745533db/cryptography-46.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9066cfd7f146f291869a9898b01df1c9b0e314bfa182cef432043f13fc462c92", size = 4300382, upload-time = "2025-10-01T00:27:29.091Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/fb/bf9571065c18c04818cb07de90c43fc042c7977c68e5de6876049559c72f/cryptography-46.0.2-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:97e83bf4f2f2c084d8dd792d13841d0a9b241643151686010866bbd076b19659", size = 4017347, upload-time = "2025-10-01T00:27:30.767Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/72/fc51856b9b16155ca071080e1a3ad0c3a8e86616daf7eb018d9565b99baa/cryptography-46.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:4a766d2a5d8127364fd936572c6e6757682fc5dfcbdba1632d4554943199f2fa", size = 4983500, upload-time = "2025-10-01T00:27:32.741Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/53/0f51e926799025e31746d454ab2e36f8c3f0d41592bc65cb9840368d3275/cryptography-46.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fab8f805e9675e61ed8538f192aad70500fa6afb33a8803932999b1049363a08", size = 4482591, upload-time = "2025-10-01T00:27:34.869Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/96/4302af40b23ab8aa360862251fb8fc450b2a06ff24bc5e261c2007f27014/cryptography-46.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1e3b6428a3d56043bff0bb85b41c535734204e599c1c0977e1d0f261b02f3ad5", size = 4300019, upload-time = "2025-10-01T00:27:37.029Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/59/0be12c7fcc4c5e34fe2b665a75bc20958473047a30d095a7657c218fa9e8/cryptography-46.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:1a88634851d9b8de8bb53726f4300ab191d3b2f42595e2581a54b26aba71b7cc", size = 4950006, upload-time = "2025-10-01T00:27:40.272Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/1d/42fda47b0111834b49e31590ae14fd020594d5e4dadd639bce89ad790fba/cryptography-46.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:be939b99d4e091eec9a2bcf41aaf8f351f312cd19ff74b5c83480f08a8a43e0b", size = 4482088, upload-time = "2025-10-01T00:27:42.668Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/17/50/60f583f69aa1602c2bdc7022dae86a0d2b837276182f8c1ec825feb9b874/cryptography-46.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f13b040649bc18e7eb37936009b24fd31ca095a5c647be8bb6aaf1761142bd1", size = 4425599, upload-time = "2025-10-01T00:27:44.616Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/57/d8d4134cd27e6e94cf44adb3f3489f935bde85f3a5508e1b5b43095b917d/cryptography-46.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bdc25e4e01b261a8fda4e98618f1c9515febcecebc9566ddf4a70c63967043b", size = 4697458, upload-time = "2025-10-01T00:27:46.209Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/2b/531e37408573e1da33adfb4c58875013ee8ac7d548d1548967d94a0ae5c4/cryptography-46.0.2-cp311-abi3-win32.whl", hash = "sha256:8b9bf67b11ef9e28f4d78ff88b04ed0929fcd0e4f70bb0f704cfc32a5c6311ee", size = 3056077, upload-time = "2025-10-01T00:27:48.424Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/cd/2f83cafd47ed2dc5a3a9c783ff5d764e9e70d3a160e0df9a9dcd639414ce/cryptography-46.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:758cfc7f4c38c5c5274b55a57ef1910107436f4ae842478c4989abbd24bd5acb", size = 3512585, upload-time = "2025-10-01T00:27:50.521Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/36/676f94e10bfaa5c5b86c469ff46d3e0663c5dc89542f7afbadac241a3ee4/cryptography-46.0.2-cp311-abi3-win_arm64.whl", hash = "sha256:218abd64a2e72f8472c2102febb596793347a3e65fafbb4ad50519969da44470", size = 2927474, upload-time = "2025-10-01T00:27:52.91Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/cc/47fc6223a341f26d103cb6da2216805e08a37d3b52bee7f3b2aee8066f95/cryptography-46.0.2-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:bda55e8dbe8533937956c996beaa20266a8eca3570402e52ae52ed60de1faca8", size = 7198626, upload-time = "2025-10-01T00:27:54.8Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/22/d66a8591207c28bbe4ac7afa25c4656dc19dc0db29a219f9809205639ede/cryptography-46.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7155c0b004e936d381b15425273aee1cebc94f879c0ce82b0d7fecbf755d53a", size = 4287584, upload-time = "2025-10-01T00:27:57.018Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/3e/fac3ab6302b928e0398c269eddab5978e6c1c50b2b77bb5365ffa8633b37/cryptography-46.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a61c154cc5488272a6c4b86e8d5beff4639cdb173d75325ce464d723cda0052b", size = 4433796, upload-time = "2025-10-01T00:27:58.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/d8/24392e5d3c58e2d83f98fe5a2322ae343360ec5b5b93fe18bc52e47298f5/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:9ec3f2e2173f36a9679d3b06d3d01121ab9b57c979de1e6a244b98d51fea1b20", size = 4292126, upload-time = "2025-10-01T00:28:00.643Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/38/3d9f9359b84c16c49a5a336ee8be8d322072a09fac17e737f3bb11f1ce64/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2fafb6aa24e702bbf74de4cb23bfa2c3beb7ab7683a299062b69724c92e0fa73", size = 3993056, upload-time = "2025-10-01T00:28:02.8Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/a3/4c44fce0d49a4703cc94bfbe705adebf7ab36efe978053742957bc7ec324/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:0c7ffe8c9b1fcbb07a26d7c9fa5e857c2fe80d72d7b9e0353dcf1d2180ae60ee", size = 4967604, upload-time = "2025-10-01T00:28:04.783Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/c2/49d73218747c8cac16bb8318a5513fde3129e06a018af3bc4dc722aa4a98/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5840f05518caa86b09d23f8b9405a7b6d5400085aa14a72a98fdf5cf1568c0d2", size = 4465367, upload-time = "2025-10-01T00:28:06.864Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1b/64/9afa7d2ee742f55ca6285a54386ed2778556a4ed8871571cb1c1bfd8db9e/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:27c53b4f6a682a1b645fbf1cd5058c72cf2f5aeba7d74314c36838c7cbc06e0f", size = 4291678, upload-time = "2025-10-01T00:28:08.982Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/48/1696d5ea9623a7b72ace87608f6899ca3c331709ac7ebf80740abb8ac673/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:512c0250065e0a6b286b2db4bbcc2e67d810acd53eb81733e71314340366279e", size = 4931366, upload-time = "2025-10-01T00:28:10.74Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/3c/9dfc778401a334db3b24435ee0733dd005aefb74afe036e2d154547cb917/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:07c0eb6657c0e9cca5891f4e35081dbf985c8131825e21d99b4f440a8f496f36", size = 4464738, upload-time = "2025-10-01T00:28:12.491Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/b1/abcde62072b8f3fd414e191a6238ce55a0050e9738090dc6cded24c12036/cryptography-46.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48b983089378f50cba258f7f7aa28198c3f6e13e607eaf10472c26320332ca9a", size = 4419305, upload-time = "2025-10-01T00:28:14.145Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/1f/3d2228492f9391395ca34c677e8f2571fb5370fe13dc48c1014f8c509864/cryptography-46.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e6f6775eaaa08c0eec73e301f7592f4367ccde5e4e4df8e58320f2ebf161ea2c", size = 4681201, upload-time = "2025-10-01T00:28:15.951Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/77/b687745804a93a55054f391528fcfc76c3d6bfd082ce9fb62c12f0d29fc1/cryptography-46.0.2-cp314-cp314t-win32.whl", hash = "sha256:e8633996579961f9b5a3008683344c2558d38420029d3c0bc7ff77c17949a4e1", size = 3022492, upload-time = "2025-10-01T00:28:17.643Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/a5/8d498ef2996e583de0bef1dcc5e70186376f00883ae27bf2133f490adf21/cryptography-46.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:48c01988ecbb32979bb98731f5c2b2f79042a6c58cc9a319c8c2f9987c7f68f9", size = 3496215, upload-time = "2025-10-01T00:28:19.272Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/56/db/ee67aaef459a2706bc302b15889a1a8126ebe66877bab1487ae6ad00f33d/cryptography-46.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:8e2ad4d1a5899b7caa3a450e33ee2734be7cc0689010964703a7c4bcc8dd4fd0", size = 2919255, upload-time = "2025-10-01T00:28:21.115Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/bb/fa95abcf147a1b0bb94d95f53fbb09da77b24c776c5d87d36f3d94521d2c/cryptography-46.0.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a08e7401a94c002e79dc3bc5231b6558cd4b2280ee525c4673f650a37e2c7685", size = 7248090, upload-time = "2025-10-01T00:28:22.846Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/66/f42071ce0e3ffbfa80a88feadb209c779fda92a23fbc1e14f74ebf72ef6b/cryptography-46.0.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d30bc11d35743bf4ddf76674a0a369ec8a21f87aaa09b0661b04c5f6c46e8d7b", size = 4293123, upload-time = "2025-10-01T00:28:25.072Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/5d/1fdbd2e5c1ba822828d250e5a966622ef00185e476d1cd2726b6dd135e53/cryptography-46.0.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bca3f0ce67e5a2a2cf524e86f44697c4323a86e0fd7ba857de1c30d52c11ede1", size = 4439524, upload-time = "2025-10-01T00:28:26.808Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/c1/5e4989a7d102d4306053770d60f978c7b6b1ea2ff8c06e0265e305b23516/cryptography-46.0.2-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff798ad7a957a5021dcbab78dfff681f0cf15744d0e6af62bd6746984d9c9e9c", size = 4297264, upload-time = "2025-10-01T00:28:29.327Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/78/b56f847d220cb1d6d6aef5a390e116ad603ce13a0945a3386a33abc80385/cryptography-46.0.2-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cb5e8daac840e8879407acbe689a174f5ebaf344a062f8918e526824eb5d97af", size = 4011872, upload-time = "2025-10-01T00:28:31.479Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/80/2971f214b066b888944f7b57761bf709ee3f2cf805619a18b18cab9b263c/cryptography-46.0.2-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:3f37aa12b2d91e157827d90ce78f6180f0c02319468a0aea86ab5a9566da644b", size = 4978458, upload-time = "2025-10-01T00:28:33.267Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/84/0cb0a2beaa4f1cbe63ebec4e97cd7e0e9f835d0ba5ee143ed2523a1e0016/cryptography-46.0.2-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e38f203160a48b93010b07493c15f2babb4e0f2319bbd001885adb3f3696d21", size = 4472195, upload-time = "2025-10-01T00:28:36.039Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/8b/2b542ddbf78835c7cd67b6fa79e95560023481213a060b92352a61a10efe/cryptography-46.0.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d19f5f48883752b5ab34cff9e2f7e4a7f216296f33714e77d1beb03d108632b6", size = 4296791, upload-time = "2025-10-01T00:28:37.732Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/12/9065b40201b4f4876e93b9b94d91feb18de9150d60bd842a16a21565007f/cryptography-46.0.2-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:04911b149eae142ccd8c9a68892a70c21613864afb47aba92d8c7ed9cc001023", size = 4939629, upload-time = "2025-10-01T00:28:39.654Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/9e/6507dc048c1b1530d372c483dfd34e7709fc542765015425f0442b08547f/cryptography-46.0.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8b16c1ede6a937c291d41176934268e4ccac2c6521c69d3f5961c5a1e11e039e", size = 4471988, upload-time = "2025-10-01T00:28:41.822Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/86/d025584a5f7d5c5ec8d3633dbcdce83a0cd579f1141ceada7817a4c26934/cryptography-46.0.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:747b6f4a4a23d5a215aadd1d0b12233b4119c4313df83ab4137631d43672cc90", size = 4422989, upload-time = "2025-10-01T00:28:43.608Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/39/536370418b38a15a61bbe413006b79dfc3d2b4b0eafceb5581983f973c15/cryptography-46.0.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b275e398ab3a7905e168c036aad54b5969d63d3d9099a0a66cc147a3cc983be", size = 4685578, upload-time = "2025-10-01T00:28:45.361Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/52/ea7e2b1910f547baed566c866fbb86de2402e501a89ecb4871ea7f169a81/cryptography-46.0.2-cp38-abi3-win32.whl", hash = "sha256:0b507c8e033307e37af61cb9f7159b416173bdf5b41d11c4df2e499a1d8e007c", size = 3036711, upload-time = "2025-10-01T00:28:47.096Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/9e/171f40f9c70a873e73c2efcdbe91e1d4b1777a03398fa1c4af3c56a2477a/cryptography-46.0.2-cp38-abi3-win_amd64.whl", hash = "sha256:f9b2dc7668418fb6f221e4bf701f716e05e8eadb4f1988a2487b11aedf8abe62", size = 3500007, upload-time = "2025-10-01T00:28:48.967Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/7c/15ad426257615f9be8caf7f97990cf3dcbb5b8dd7ed7e0db581a1c4759dd/cryptography-46.0.2-cp38-abi3-win_arm64.whl", hash = "sha256:91447f2b17e83c9e0c89f133119d83f94ce6e0fb55dd47da0a959316e6e9cfa1", size = 2918153, upload-time = "2025-10-01T00:28:51.003Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dateparser"
|
name = "dateparser"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -345,6 +495,19 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hcloud"
|
||||||
|
version = "2.8.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "python-dateutil" },
|
||||||
|
{ name = "requests" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/08/a4/af4871851f1a01a048cbe4a55c7404b1f6929ab6616f3f6239f8b58d0a2e/hcloud-2.8.0.tar.gz", hash = "sha256:e5e86ac39a84473479f9109eb5682ef67a6374c1431d158c3bf74432a0883d2b", size = 136160, upload-time = "2025-10-07T08:21:04.601Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/de/95b8aac296fec6818236eb1cc173ff699ebc597cd160a410743f655cc5ac/hcloud-2.8.0-py3-none-any.whl", hash = "sha256:575db85fbb2558851a9ffcbaacbf2fdf744a097cb05debec0a009913771236fb", size = 97523, upload-time = "2025-10-07T08:21:03.048Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httptools"
|
name = "httptools"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
@@ -387,6 +550,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "invoke"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/de/bd/b461d3424a24c80490313fd77feeb666ca4f6a28c7e72713e3d9095719b4/invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707", size = 304762, upload-time = "2025-10-11T00:36:35.172Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/4b/b99e37f88336009971405cbb7630610322ed6fbfa31e1d7ab3fbf3049a2d/invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8", size = 160287, upload-time = "2025-10-11T00:36:33.703Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipykernel"
|
name = "ipykernel"
|
||||||
version = "6.30.1"
|
version = "6.30.1"
|
||||||
@@ -648,8 +820,12 @@ name = "materia"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "hcloud" },
|
||||||
|
{ name = "paramiko" },
|
||||||
{ name = "pyarrow" },
|
{ name = "pyarrow" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
|
{ name = "pyyaml" },
|
||||||
|
{ name = "typer" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
@@ -667,8 +843,12 @@ exploration = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "hcloud", specifier = ">=2.3.0" },
|
||||||
|
{ name = "paramiko", specifier = ">=3.5.0" },
|
||||||
{ name = "pyarrow", specifier = ">=20.0.0" },
|
{ name = "pyarrow", specifier = ">=20.0.0" },
|
||||||
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
||||||
|
{ name = "pyyaml", specifier = ">=6.0.2" },
|
||||||
|
{ name = "typer", specifier = ">=0.15.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
@@ -823,6 +1003,21 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/cd/d7/612123674d7b17cf345aad0a10289b2a384bff404e0463a83c4a3a59d205/pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370", size = 13186141, upload-time = "2025-08-21T10:28:05.377Z" },
|
{ url = "https://files.pythonhosted.org/packages/cd/d7/612123674d7b17cf345aad0a10289b2a384bff404e0463a83c4a3a59d205/pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370", size = 13186141, upload-time = "2025-08-21T10:28:05.377Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paramiko"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "bcrypt" },
|
||||||
|
{ name = "cryptography" },
|
||||||
|
{ name = "invoke" },
|
||||||
|
{ name = "pynacl" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1f/e7/81fdcbc7f190cdb058cffc9431587eb289833bdd633e2002455ca9bb13d4/paramiko-4.0.0.tar.gz", hash = "sha256:6a25f07b380cc9c9a88d2b920ad37167ac4667f8d9886ccebd8f90f654b5d69f", size = 1630743, upload-time = "2025-08-04T01:02:03.711Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/90/a744336f5af32c433bd09af7854599682a383b37cfd78f7de263de6ad6cb/paramiko-4.0.0-py3-none-any.whl", hash = "sha256:0e20e00ac666503bf0b4eda3b6d833465a2b7aff2e2b3d79a8bba5ef144ee3b9", size = 223932, upload-time = "2025-08-04T01:02:02.029Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parso"
|
name = "parso"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
@@ -1129,6 +1324,43 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pynacl"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/06/c6/a3124dee667a423f2c637cfd262a54d67d8ccf3e160f3c50f622a85b7723/pynacl-1.6.0.tar.gz", hash = "sha256:cb36deafe6e2bce3b286e5d1f3e1c246e0ccdb8808ddb4550bb2792f2df298f2", size = 3505641, upload-time = "2025-09-10T23:39:22.308Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/24/1b639176401255605ba7c2b93a7b1eb1e379e0710eca62613633eb204201/pynacl-1.6.0-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:f46386c24a65383a9081d68e9c2de909b1834ec74ff3013271f1bca9c2d233eb", size = 384141, upload-time = "2025-09-10T23:38:28.675Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/7b/874efdf57d6bf172db0df111b479a553c3d9e8bb4f1f69eb3ffff772d6e8/pynacl-1.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dea103a1afcbc333bc0e992e64233d360d393d1e63d0bc88554f572365664348", size = 808132, upload-time = "2025-09-10T23:38:38.995Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/61/9b53f5913f3b75ac3d53170cdb897101b2b98afc76f4d9d3c8de5aa3ac05/pynacl-1.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:04f20784083014e265ad58c1b2dd562c3e35864b5394a14ab54f5d150ee9e53e", size = 1407253, upload-time = "2025-09-10T23:38:40.492Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/0a/b138916b22bbf03a1bdbafecec37d714e7489dd7bcaf80cd17852f8b67be/pynacl-1.6.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbcc4452a1eb10cd5217318c822fde4be279c9de8567f78bad24c773c21254f8", size = 843719, upload-time = "2025-09-10T23:38:30.87Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/3b/17c368197dfb2c817ce033f94605a47d0cc27901542109e640cef263f0af/pynacl-1.6.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51fed9fe1bec9e7ff9af31cd0abba179d0e984a2960c77e8e5292c7e9b7f7b5d", size = 1445441, upload-time = "2025-09-10T23:38:33.078Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/3c/f79b185365ab9be80cd3cd01dacf30bf5895f9b7b001e683b369e0bb6d3d/pynacl-1.6.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:10d755cf2a455d8c0f8c767a43d68f24d163b8fe93ccfaabfa7bafd26be58d73", size = 825691, upload-time = "2025-09-10T23:38:34.832Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/1f/8b37d25e95b8f2a434a19499a601d4d272b9839ab8c32f6b0fc1e40c383f/pynacl-1.6.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:536703b8f90e911294831a7fbcd0c062b837f3ccaa923d92a6254e11178aaf42", size = 1410726, upload-time = "2025-09-10T23:38:36.893Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/93/5a4a4cf9913014f83d615ad6a2df9187330f764f606246b3a744c0788c03/pynacl-1.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6b08eab48c9669d515a344fb0ef27e2cbde847721e34bba94a343baa0f33f1f4", size = 801035, upload-time = "2025-09-10T23:38:42.109Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/60/40da6b0fe6a4d5fd88f608389eb1df06492ba2edca93fca0b3bebff9b948/pynacl-1.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5789f016e08e5606803161ba24de01b5a345d24590a80323379fc4408832d290", size = 1371854, upload-time = "2025-09-10T23:38:44.16Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/b2/37ac1d65008f824cba6b5bf68d18b76d97d0f62d7a032367ea69d4a187c8/pynacl-1.6.0-cp314-cp314t-win32.whl", hash = "sha256:4853c154dc16ea12f8f3ee4b7e763331876316cc3a9f06aeedf39bcdca8f9995", size = 230345, upload-time = "2025-09-10T23:38:48.276Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/5a/9234b7b45af890d02ebee9aae41859b9b5f15fb4a5a56d88e3b4d1659834/pynacl-1.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:347dcddce0b4d83ed3f32fd00379c83c425abee5a9d2cd0a2c84871334eaff64", size = 243103, upload-time = "2025-09-10T23:38:45.503Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/2c/c1a0f19d720ab0af3bc4241af2bdf4d813c3ecdcb96392b5e1ddf2d8f24f/pynacl-1.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2d6cd56ce4998cb66a6c112fda7b1fdce5266c9f05044fa72972613bef376d15", size = 187778, upload-time = "2025-09-10T23:38:46.731Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/37/87c72df19857c5b3b47ace6f211a26eb862ada495cc96daa372d96048fca/pynacl-1.6.0-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:f4b3824920e206b4f52abd7de621ea7a44fd3cb5c8daceb7c3612345dfc54f2e", size = 382610, upload-time = "2025-09-10T23:38:49.459Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/64/3ce958a5817fd3cc6df4ec14441c43fd9854405668d73babccf77f9597a3/pynacl-1.6.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:16dd347cdc8ae0b0f6187a2608c0af1c8b7ecbbe6b4a06bff8253c192f696990", size = 798744, upload-time = "2025-09-10T23:38:58.531Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/8a/3f0dd297a0a33fa3739c255feebd0206bb1df0b44c52fbe2caf8e8bc4425/pynacl-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16c60daceee88d04f8d41d0a4004a7ed8d9a5126b997efd2933e08e93a3bd850", size = 1397879, upload-time = "2025-09-10T23:39:00.44Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/94/028ff0434a69448f61348d50d2c147dda51aabdd4fbc93ec61343332174d/pynacl-1.6.0-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25720bad35dfac34a2bcdd61d9e08d6bfc6041bebc7751d9c9f2446cf1e77d64", size = 833907, upload-time = "2025-09-10T23:38:50.936Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/bc/a5cff7f8c30d5f4c26a07dfb0bcda1176ab8b2de86dda3106c00a02ad787/pynacl-1.6.0-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8bfaa0a28a1ab718bad6239979a5a57a8d1506d0caf2fba17e524dbb409441cf", size = 1436649, upload-time = "2025-09-10T23:38:52.783Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/20/c397be374fd5d84295046e398de4ba5f0722dc14450f65db76a43c121471/pynacl-1.6.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ef214b90556bb46a485b7da8258e59204c244b1b5b576fb71848819b468c44a7", size = 817142, upload-time = "2025-09-10T23:38:54.4Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/30/5efcef3406940cda75296c6d884090b8a9aad2dcc0c304daebb5ae99fb4a/pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:49c336dd80ea54780bcff6a03ee1a476be1612423010472e60af83452aa0f442", size = 1401794, upload-time = "2025-09-10T23:38:56.614Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/e1/a8fe1248cc17ccb03b676d80fa90763760a6d1247da434844ea388d0816c/pynacl-1.6.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f3482abf0f9815e7246d461fab597aa179b7524628a4bc36f86a7dc418d2608d", size = 772161, upload-time = "2025-09-10T23:39:01.93Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/76/8a62702fb657d6d9104ce13449db221a345665d05e6a3fdefb5a7cafd2ad/pynacl-1.6.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:140373378e34a1f6977e573033d1dd1de88d2a5d90ec6958c9485b2fd9f3eb90", size = 1370720, upload-time = "2025-09-10T23:39:03.531Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/38/9e9e9b777a1c4c8204053733e1a0269672c0bd40852908c9ad6b6eaba82c/pynacl-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6b393bc5e5a0eb86bb85b533deb2d2c815666665f840a09e0aa3362bb6088736", size = 791252, upload-time = "2025-09-10T23:39:05.058Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/ef/d972ce3d92ae05c9091363cf185e8646933f91c376e97b8be79ea6e96c22/pynacl-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a25cfede801f01e54179b8ff9514bd7b5944da560b7040939732d1804d25419", size = 1362910, upload-time = "2025-09-10T23:39:06.924Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/2c/ee0b373a1861f66a7ca8bdb999331525615061320dd628527a50ba8e8a60/pynacl-1.6.0-cp38-abi3-win32.whl", hash = "sha256:dcdeb41c22ff3c66eef5e63049abf7639e0db4edee57ba70531fc1b6b133185d", size = 226461, upload-time = "2025-09-10T23:39:11.894Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/f7/41b6c0b9dd9970173b6acc026bab7b4c187e4e5beef2756d419ad65482da/pynacl-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:cf831615cc16ba324240de79d925eacae8265b7691412ac6b24221db157f6bd1", size = 238802, upload-time = "2025-09-10T23:39:08.966Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/0f/462326910c6172fa2c6ed07922b22ffc8e77432b3affffd9e18f444dbfbb/pynacl-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:84709cea8f888e618c21ed9a0efdb1a59cc63141c403db8bf56c469b71ad56f2", size = 183846, upload-time = "2025-09-10T23:39:10.552Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
version = "2.9.0.post0"
|
version = "2.9.0.post0"
|
||||||
@@ -1402,6 +1634,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" },
|
{ url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shellingham"
|
||||||
|
version = "1.5.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "six"
|
name = "six"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
@@ -1634,6 +1875,21 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
|
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typer"
|
||||||
|
version = "0.19.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "shellingham" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/21/ca/950278884e2ca20547ff3eb109478c6baf6b8cf219318e6bc4f666fad8e8/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca", size = 104755, upload-time = "2025-09-23T09:47:48.256Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.14.1"
|
version = "4.14.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user