Initial commit
This commit is contained in:
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.14
|
||||
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"python.testing.pytestArgs": [
|
||||
"tests"
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true
|
||||
}
|
||||
83
AGENTS.md
Normal file
83
AGENTS.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# AI Agents - Project Rules
|
||||
|
||||
**Document Version:** v4 (independent, incremented on structural changes)
|
||||
|
||||
Rules and instructions for AI assistants (Claude Code, Cursor, Copilot, etc.)
|
||||
|
||||
## First-time setup
|
||||
|
||||
- **On first read of this file, immediately read all other `.md` files in the project root** (e.g. `PROJECT.md`, `CHANGELOG.md`, `DESIGN_DOCUMENT.md`) to get full project context before starting any task.
|
||||
|
||||
## Language
|
||||
|
||||
- **Always write all documentation in English**
|
||||
|
||||
## Dependency Management
|
||||
|
||||
- **Always use `poetry add`** to add dependencies, **never edit `pyproject.toml` directly**
|
||||
```bash
|
||||
poetry add requests
|
||||
poetry add --group dev pytest
|
||||
```
|
||||
- Use `poetry remove` to remove dependencies — **never edit `pyproject.toml` manually**
|
||||
|
||||
## Project Structure
|
||||
|
||||
- Entry points are in the project root (named after project or by purpose: `project_name.py`, `cli.py`, `gui.py`, `server.py`)
|
||||
- A project can have multiple entry points
|
||||
- All modules belong in the `src/` folder
|
||||
- Tests belong in the `tests/` folder
|
||||
- Virtual environment is in `.venv/` (do not copy, do not generate)
|
||||
|
||||
## Code
|
||||
|
||||
- Always use type annotations
|
||||
- Follow PEP8 and format with Ruff (88 characters per line)
|
||||
- Before commit run `poetry run ruff check` and `poetry run mypy`
|
||||
|
||||
## Testing
|
||||
|
||||
- **Use pytest exclusively** - never use the `unittest` module
|
||||
- No `unittest.TestCase` classes, no `self.assert*` methods
|
||||
- Use plain `assert` statements and pytest fixtures
|
||||
|
||||
## Running
|
||||
|
||||
- Use `poetry run` to run scripts:
|
||||
```bash
|
||||
poetry run python project_name.py
|
||||
poetry run pytest
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
- Use **loguru** for logging - never use `print()` for debugging
|
||||
- Never log secrets, passwords, tokens, or API keys
|
||||
|
||||
## Environment and Secrets
|
||||
|
||||
- Store secrets in `.env` file with `ENV_DEBUG=true/false` variable
|
||||
- Load secrets using `python-dotenv` and `os.getenv()`
|
||||
- **Never commit `.env` file**
|
||||
|
||||
## Git
|
||||
|
||||
- `.gitignore` should contain: `.venv/`, `__pycache__/`, `*.pyc`, `.mypy_cache/`, `.env`
|
||||
- Do not commit `poetry.lock` only if it's a library (for applications, commit it)
|
||||
- **Never commit this documentation** (`DESIGN_DOCUMENT.md`, `AGENTS.md`, `.claudeignore`)
|
||||
- `PROJECT.md` **should be committed** - it's project-specific
|
||||
|
||||
## Versioning
|
||||
|
||||
- **Always ask user before bumping version** - never increase version automatically
|
||||
- **Keep `CHANGELOG.md` updated** - document all significant changes as they are made
|
||||
- Update `CHANGELOG.md` with changes before version bump
|
||||
- Version is defined in `pyproject.toml` under `[project]` section
|
||||
- Follow semantic versioning (MAJOR.MINOR.PATCH)
|
||||
|
||||
## Task Management
|
||||
|
||||
- **When completing tasks, mark them as done** - if you finish any task with a checkbox anywhere in project documentation, check it off as completed `[ ]` → `[x]`
|
||||
- **Track all work** - this applies to tasks in `PROJECT.md` (TODO section, Development Roadmap, any checklists) and other documentation
|
||||
- **Update documentation** - when completing changes, update relevant sections in `PROJECT.md`, `CHANGELOG.md`, and architecture diagrams
|
||||
- **Keep task lists current** - completed items with `[x]` stay visible to show progress history
|
||||
10
CLAUDE.md
Normal file
10
CLAUDE.md
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
## First-time setup
|
||||
|
||||
**At the start of every new session, read all of the following files before doing anything else:**
|
||||
|
||||
- `CLAUDE.md` (this file)
|
||||
- `AGENTS.md`
|
||||
- `PROJECT.md`
|
||||
- `CHANGELOG.md`
|
||||
- `DESIGN_DOCUMENT.md`
|
||||
260
DESIGN_DOCUMENT_MODULE.md
Normal file
260
DESIGN_DOCUMENT_MODULE.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# Python Library Development Guidelines
|
||||
|
||||
**Document Version:** v1
|
||||
|
||||
> **Note on Versioning:**
|
||||
> - This document version is independent — reused across projects
|
||||
> - **Project version** source of truth: `pyproject.toml` under `[project]`
|
||||
> - `CHANGELOG.md` uses project version from `pyproject.toml`
|
||||
|
||||
## Related Documents
|
||||
|
||||
- **README.md** — Project overview, public API description, installation and usage examples
|
||||
- **AGENTS.md** — Rules for AI assistants
|
||||
- **PROJECT.md** — Project goals and current state
|
||||
- **CHANGELOG.md** — Version history
|
||||
|
||||
### Documentation Organization
|
||||
|
||||
All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root.
|
||||
|
||||
The root directory contains only the core documents: `DESIGN_DOCUMENT_MODULE.md`, `AGENTS.md`, `PROJECT.md`, `CHANGELOG.md`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Code Style
|
||||
|
||||
- **PEP8** with 150-character lines (Ruff)
|
||||
- **4 spaces** indentation
|
||||
- **snake_case** functions/variables, **PascalCase** classes, **SCREAMING_SNAKE_CASE** constants
|
||||
- **Type hints** required on all functions
|
||||
- **Import order**: stdlib → third-party → local
|
||||
|
||||
---
|
||||
|
||||
## 2. SOLID Principles
|
||||
|
||||
- **SRP** — One class = one responsibility
|
||||
- **OCP** — Open for extension, closed for modification
|
||||
- **LSP** — Subclasses substitutable for parents
|
||||
- **ISP** — Small interfaces over large ones
|
||||
- **DIP** — Depend on abstractions
|
||||
|
||||
---
|
||||
|
||||
## 3. Dependency Injection
|
||||
|
||||
Pass dependencies via constructor. Never instantiate dependencies inside a class.
|
||||
|
||||
---
|
||||
|
||||
## 4. Protocols Over Inheritance
|
||||
|
||||
Prefer `typing.Protocol` and composition over class inheritance.
|
||||
|
||||
---
|
||||
|
||||
## 5. Data Classes
|
||||
|
||||
Use `@dataclass` for internal data structures, `pydantic.BaseModel` for data that requires validation.
|
||||
|
||||
---
|
||||
|
||||
## 6. Logging
|
||||
|
||||
This is a library. Libraries must **never configure logging sinks** — that is the responsibility of the consuming application.
|
||||
|
||||
### Usage in library code
|
||||
|
||||
```python
|
||||
from loguru import logger
|
||||
|
||||
def some_function() -> None:
|
||||
logger.debug("Detail message")
|
||||
logger.info("Milestone message")
|
||||
```
|
||||
|
||||
Always use the module-level `logger` from loguru directly. Never call `logger.add()` or `logger.remove()` inside library code.
|
||||
|
||||
### Behavior for consumers
|
||||
|
||||
Loguru has a default sink to `stderr` enabled. Consumers who also use loguru get library logs automatically in their configured sinks. Consumers who want to suppress or filter library logs use the package name as the identifier:
|
||||
|
||||
```python
|
||||
from loguru import logger
|
||||
|
||||
logger.disable("<package_name>") # suppress all
|
||||
logger.add(sys.stderr, filter={"<package_name>": "WARNING"}) # WARNING and above only
|
||||
logger.enable("<package_name>") # re-enable
|
||||
```
|
||||
|
||||
This must be documented in `README.md`.
|
||||
|
||||
### Rules
|
||||
|
||||
- Never call `logger.add()` inside library code — no file sinks, no stdout sinks
|
||||
- Never call `logger.remove()` inside library code
|
||||
- Never log secrets, passwords, tokens, or API keys
|
||||
- Never use `print()` inside library code — not for debugging, not for output
|
||||
|
||||
#### Log levels
|
||||
|
||||
| Level | When to use |
|
||||
|-------|-------------|
|
||||
| `DEBUG` | Per-item detail: individual operations, cache hits, internal state |
|
||||
| `INFO` | Significant milestones visible to the consuming application |
|
||||
| `WARNING` | Recoverable issues: fallback used, unexpected but non-fatal state |
|
||||
| `ERROR` | Failures the caller must know about — operation cannot continue |
|
||||
|
||||
---
|
||||
|
||||
## 7. Environment and Secrets
|
||||
|
||||
- Libraries do not use `.env` files or `python-dotenv`
|
||||
- Configuration is passed by the caller via arguments or constructor parameters
|
||||
- Never read `os.getenv()` inside library code unless explicitly documented as a supported configuration mechanism
|
||||
|
||||
---
|
||||
|
||||
## 8. Error Handling
|
||||
|
||||
Define specific exception types in `src/<package>/exceptions.py`. Use a fail-fast approach — surface errors early rather than silently continuing. All public exceptions must be exported from the top-level `__init__.py`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Testing
|
||||
|
||||
- **pytest** only — no `unittest`, no `TestCase` classes, no `self.assert*`
|
||||
- Arrange-Act-Assert pattern
|
||||
- Test naming: `test_<action>_<context>`
|
||||
- Do not commit `poetry.lock` — this is a library; consumers pin their own dependencies
|
||||
|
||||
---
|
||||
|
||||
## 10. Tooling
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| **Ruff** | Formatting and linting |
|
||||
| **mypy** | Static type checking |
|
||||
| **pytest** | Testing |
|
||||
|
||||
Run before every commit:
|
||||
```bash
|
||||
poetry run ruff check
|
||||
poetry run mypy
|
||||
poetry run pytest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Poetry
|
||||
|
||||
```bash
|
||||
poetry install # Install all dependencies
|
||||
poetry add <pkg> # Add runtime dependency
|
||||
poetry add --group dev <pkg> # Add dev dependency
|
||||
poetry remove <pkg> # Remove dependency
|
||||
poetry run <cmd> # Run command in virtualenv
|
||||
poetry build # Build sdist and wheel into dist/
|
||||
poetry publish # Publish to PyPI
|
||||
```
|
||||
|
||||
Never edit `pyproject.toml` directly to add or remove dependencies.
|
||||
|
||||
### pyproject.toml configuration for a library
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "your-library"
|
||||
version = "0.1.0"
|
||||
description = "Short description"
|
||||
requires-python = ">=3.x"
|
||||
dependencies = []
|
||||
|
||||
[tool.poetry]
|
||||
packages = [{include = "your_library", from = "src"}]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
```
|
||||
|
||||
Do **not** add `[tool.poetry.scripts]` — libraries have no entry points.
|
||||
|
||||
### poetry.lock
|
||||
|
||||
Do **not** commit `poetry.lock` for libraries. It is only committed for applications. Add `poetry.lock` to `.gitignore`.
|
||||
|
||||
---
|
||||
|
||||
## 12. Project Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── src/
|
||||
│ └── your_library/
|
||||
│ ├── __init__.py # Public API — export everything the caller needs
|
||||
│ ├── exceptions.py # All public exception types
|
||||
│ └── ... # Internal modules
|
||||
├── tests/ # Tests
|
||||
├── docs/ # Detailed documentation
|
||||
├── .venv/ # Virtual environment (managed by Poetry)
|
||||
└── pyproject.toml # Project config and dependencies
|
||||
```
|
||||
|
||||
- No entry point scripts in the project root — this is a library, not an application
|
||||
- `__init__.py` defines the public API; callers import from the top-level package only
|
||||
|
||||
---
|
||||
|
||||
## 13. Public API
|
||||
|
||||
- Everything intended for external use must be exported from `src/<package>/__init__.py`
|
||||
- Use `__all__` to explicitly declare the public surface
|
||||
- Internal modules are prefixed with `_` or kept unexported
|
||||
- Public API must be stable across patch versions; breaking changes require a major version bump
|
||||
|
||||
---
|
||||
|
||||
## 14. Distribution
|
||||
|
||||
Build and publish with Poetry:
|
||||
|
||||
```bash
|
||||
poetry build # Creates dist/*.tar.gz and dist/*.whl
|
||||
poetry publish # Publishes to PyPI (requires credentials)
|
||||
```
|
||||
|
||||
`dist/` is **not committed** to the repository — add it to `.gitignore`.
|
||||
|
||||
---
|
||||
|
||||
## 15. Versioning
|
||||
|
||||
- Follow **semantic versioning**: `MAJOR.MINOR.PATCH`
|
||||
- Version is defined in `pyproject.toml` under `[project]`
|
||||
- Always ask before bumping the version — never increment automatically
|
||||
- Update `CHANGELOG.md` before bumping the version
|
||||
- Breaking changes to the public API require a major version bump
|
||||
|
||||
---
|
||||
|
||||
## 16. Documentation and Task Management
|
||||
|
||||
- Keep `PROJECT.md` and `CHANGELOG.md` up to date when making changes
|
||||
- `README.md` must contain installation instructions and usage examples for the public API
|
||||
- Document architectural changes in this file or in `docs/`
|
||||
|
||||
### Task notation
|
||||
|
||||
Tasks are written as single-line comments directly in code, or in `PROJECT.md` for cross-cutting concerns:
|
||||
|
||||
```python
|
||||
# TODO: one-liner description of a task to be done
|
||||
# FIXME: one-liner description of a known bug to be fixed
|
||||
```
|
||||
|
||||
No other task format is used — no checkboxes, no numbered lists in documentation.
|
||||
|
||||
If a `# TODO:` comment already exists at a specific location in code, do not repeat it in `PROJECT.md`.
|
||||
88
PROJECT.md
Normal file
88
PROJECT.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# PROJECT.md
|
||||
|
||||
## Project Overview
|
||||
|
||||
**PlanetaryTime** is a Python library for representing and working with time on other bodies in the Solar System, similar in spirit to the standard `datetime` module.
|
||||
|
||||
---
|
||||
|
||||
## Core Concept
|
||||
|
||||
Time on a given planetary body is expressed using a human-readable clock where **the hour is preserved as close to one Earth hour as possible**.
|
||||
|
||||
The number of hours in a planetary day is derived from the length of that body's sidereal rotation period expressed in Earth hours, rounded to the nearest integer:
|
||||
|
||||
```
|
||||
hours_per_day = round(rotation_period_in_earth_hours)
|
||||
```
|
||||
|
||||
Example: if a body rotates once every 24.6 Earth hours, its day has 25 hours, each approximately 1 Earth hour long.
|
||||
|
||||
---
|
||||
|
||||
## Epoch
|
||||
|
||||
The starting point for counting days (sol 0 / day 0) is configurable per body and per use case:
|
||||
|
||||
- **Discovery epoch** — the date the body was officially discovered
|
||||
- **Contact epoch** — the date of first contact with the body:
|
||||
- landing of an automated probe, or
|
||||
- landing of a crewed mission
|
||||
|
||||
The epoch is set when constructing a planetary time object and determines what "day 1" means.
|
||||
|
||||
---
|
||||
|
||||
## Planned Public API
|
||||
|
||||
```python
|
||||
from planetarytime import PlanetaryTime, Body, Epoch
|
||||
|
||||
# construct from Earth datetime + target body + epoch choice
|
||||
pt = PlanetaryTime.from_earth(datetime.utcnow(), body=Body.MARS, epoch=Epoch.DISCOVERY)
|
||||
|
||||
pt.day # integer day number since epoch
|
||||
pt.hour # hour within the current day (0-indexed)
|
||||
pt.minute # minute within the current hour
|
||||
pt.second # second within the current minute
|
||||
pt.body # the planetary body
|
||||
pt.epoch # the epoch in use
|
||||
|
||||
str(pt) # human-readable: e.g. "Sol 142, 09:35:22 (Mars / Discovery epoch)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Supported Bodies
|
||||
|
||||
Initial target: planets and major bodies of the Solar System with known rotation periods.
|
||||
|
||||
| Body | Rotation period (Earth h) | Hours per day |
|
||||
|---------|--------------------------|---------------|
|
||||
| Mercury | 1407.6 | 1408 |
|
||||
| Venus | 5832.5 (retrograde) | 5833 |
|
||||
| Mars | 24.6 | 25 |
|
||||
| Jupiter | 9.9 | 10 |
|
||||
| Saturn | 10.7 | 11 |
|
||||
| Uranus | 17.2 | 17 |
|
||||
| Neptune | 16.1 | 16 |
|
||||
|
||||
Moon and dwarf planets (Pluto, Ceres, Eris) may be added later.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
- Project scaffolded (Poetry, src layout, tests directory)
|
||||
- No implementation yet
|
||||
|
||||
## TODO
|
||||
|
||||
- Define `Body` enum with rotation periods
|
||||
- Define `Epoch` enum and epoch registry
|
||||
- Implement core `PlanetaryTime` class
|
||||
- Implement conversion from Earth `datetime`
|
||||
- Implement `__str__` / `__repr__`
|
||||
- Write tests for conversion accuracy
|
||||
- Write tests for epoch switching
|
||||
- Populate README with usage examples
|
||||
8
PlanetaryTime.code-workspace
Normal file
8
PlanetaryTime.code-workspace
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
51
README.md
Normal file
51
README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# PlanetaryTime
|
||||
|
||||
<!-- TODO: short description of the library -->
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install planetarytime
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
import planetarytime
|
||||
|
||||
# TODO: usage examples
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
This library uses [loguru](https://github.com/Delgan/loguru) for internal logging.
|
||||
|
||||
By default, loguru has a sink to `stderr` enabled. If your application also uses
|
||||
loguru, library logs will appear in your configured sinks automatically — no extra
|
||||
setup needed.
|
||||
|
||||
### Suppressing library logs
|
||||
|
||||
To silence all logs from this library:
|
||||
|
||||
```python
|
||||
from loguru import logger
|
||||
logger.disable("<package_name>")
|
||||
```
|
||||
|
||||
To re-enable them:
|
||||
|
||||
```python
|
||||
logger.enable("<package_name>")
|
||||
```
|
||||
|
||||
### Filtering by level
|
||||
|
||||
If you want library logs only from `WARNING` and above, filter by the package name:
|
||||
|
||||
```python
|
||||
from loguru import logger
|
||||
import sys
|
||||
|
||||
logger.add(sys.stderr, filter={"<package_name>": "WARNING"})
|
||||
```
|
||||
370
poetry.lock
generated
Normal file
370
poetry.lock
generated
Normal file
@@ -0,0 +1,370 @@
|
||||
# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
groups = ["main", "dev"]
|
||||
markers = "sys_platform == \"win32\""
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
description = "brain-dead simple config-ini parsing"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
|
||||
{file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "librt"
|
||||
version = "0.9.0"
|
||||
description = "Mypyc runtime library"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
markers = "platform_python_implementation != \"PyPy\""
|
||||
files = [
|
||||
{file = "librt-0.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f8e12706dcb8ff6b3ed57514a19e45c49ad00bcd423e87b2b2e4b5f64578443"},
|
||||
{file = "librt-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e3dda8345307fd7306db0ed0cb109a63a2c85ba780eb9dc2d09b2049a931f9c"},
|
||||
{file = "librt-0.9.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:de7dac64e3eb832ffc7b840eb8f52f76420cde1b845be51b2a0f6b870890645e"},
|
||||
{file = "librt-0.9.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22a904cbdb678f7cb348c90d543d3c52f581663d687992fee47fd566dcbf5285"},
|
||||
{file = "librt-0.9.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:224b9727eb8bc188bc3bcf29d969dba0cd61b01d9bac80c41575520cc4baabb2"},
|
||||
{file = "librt-0.9.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e94cbc6ad9a6aeea46d775cbb11f361022f778a9cc8cc90af653d3a594b057ce"},
|
||||
{file = "librt-0.9.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7bc30ad339f4e1a01d4917d645e522a0bc0030644d8973f6346397c93ba1503f"},
|
||||
{file = "librt-0.9.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:56d65b583cf43b8cf4c8fbe1e1da20fa3076cc32a1149a141507af1062718236"},
|
||||
{file = "librt-0.9.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0a1be03168b2691ba61927e299b352a6315189199ca18a57b733f86cb3cc8d38"},
|
||||
{file = "librt-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:63c12efcd160e1d14da11af0c46c0217473e1e0d2ae1acbccc83f561ea4c2a7b"},
|
||||
{file = "librt-0.9.0-cp310-cp310-win32.whl", hash = "sha256:e9002e98dcb1c0a66723592520decd86238ddcef168b37ff6cfb559200b4b774"},
|
||||
{file = "librt-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:9fcb461fbf70654a52a7cc670e606f04449e2374c199b1825f754e16dacfedd8"},
|
||||
{file = "librt-0.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90904fac73c478f4b83f4ed96c99c8208b75e6f9a8a1910548f69a00f1eaa671"},
|
||||
{file = "librt-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:789fff71757facc0738e8d89e3b84e4f0251c1c975e85e81b152cdaca927cc2d"},
|
||||
{file = "librt-0.9.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1bf465d1e5b0a27713862441f6467b5ab76385f4ecf8f1f3a44f8aa3c695b4b6"},
|
||||
{file = "librt-0.9.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f819e0c6413e259a17a7c0d49f97f405abadd3c2a316a3b46c6440b7dbbedbb1"},
|
||||
{file = "librt-0.9.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0785c2fb4a81e1aece366aa3e2e039f4a4d7d21aaaded5227d7f3c703427882"},
|
||||
{file = "librt-0.9.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80b25c7b570a86c03b5da69e665809deb39265476e8e21d96a9328f9762f9990"},
|
||||
{file = "librt-0.9.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d4d16b608a1c43d7e33142099a75cd93af482dadce0bf82421e91cad077157f4"},
|
||||
{file = "librt-0.9.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:194fc1a32e1e21fe809d38b5faea66cc65eaa00217c8901fbdb99866938adbdb"},
|
||||
{file = "librt-0.9.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8c6bc1384d9738781cfd41d09ad7f6e8af13cfea2c75ece6bd6d2566cdea2076"},
|
||||
{file = "librt-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cb151e52a044f06e54ac7f7b47adbfc89b5c8e2b63e1175a9d587c43e8942a"},
|
||||
{file = "librt-0.9.0-cp311-cp311-win32.whl", hash = "sha256:f100bfe2acf8a3689af9d0cc660d89f17286c9c795f9f18f7b62dd1a6b247ae6"},
|
||||
{file = "librt-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:0b73e4266307e51c95e09c0750b7ec383c561d2e97d58e473f6f6a209952fbb8"},
|
||||
{file = "librt-0.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc5518873822d2faa8ebdd2c1a4d7c8ef47b01a058495ab7924cb65bdbf5fc9a"},
|
||||
{file = "librt-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b3e3bc363f71bda1639a4ee593cb78f7fbfeacc73411ec0d4c92f00730010a4"},
|
||||
{file = "librt-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a09c2f5869649101738653a9b7ab70cf045a1105ac66cbb8f4055e61df78f2d"},
|
||||
{file = "librt-0.9.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ca8e133d799c948db2ab1afc081c333a825b5540475164726dcbf73537e5c2f"},
|
||||
{file = "librt-0.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:603138ee838ee1583f1b960b62d5d0007845c5c423feb68e44648b1359014e27"},
|
||||
{file = "librt-0.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4003f70c56a5addd6aa0897f200dd59afd3bf7bcd5b3cce46dd21f925743bc2"},
|
||||
{file = "librt-0.9.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:78042f6facfd98ecb25e9829c7e37cce23363d9d7c83bc5f72702c5059eb082b"},
|
||||
{file = "librt-0.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a361c9434a64d70a7dbb771d1de302c0cc9f13c0bffe1cf7e642152814b35265"},
|
||||
{file = "librt-0.9.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dd2c7e082b0b92e1baa4da28163a808672485617bc855cc22a2fd06978fa9084"},
|
||||
{file = "librt-0.9.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7e6274fd33fc5b2a14d41c9119629d3ff395849d8bcbc80cf637d9e8d2034da8"},
|
||||
{file = "librt-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5093043afb226ecfa1400120d1ebd4442b4f99977783e4f4f7248879009b227f"},
|
||||
{file = "librt-0.9.0-cp312-cp312-win32.whl", hash = "sha256:9edcc35d1cae9fd5320171b1a838c7da8a5c968af31e82ecc3dff30b4be0957f"},
|
||||
{file = "librt-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc2917258e131ae5f958a4d872e07555b51cb7466a43433218061c74ef33745"},
|
||||
{file = "librt-0.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:90e6d5420fc8a300518d4d2288154ff45005e920425c22cbbfe8330f3f754bd9"},
|
||||
{file = "librt-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29b68cd9714531672db62cc54f6e8ff981900f824d13fa0e00749189e13778e"},
|
||||
{file = "librt-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d5c8a5929ac325729f6119802070b561f4db793dffc45e9ac750992a4ed4d22"},
|
||||
{file = "librt-0.9.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:756775d25ec8345b837ab52effee3ad2f3b2dfd6bbee3e3f029c517bd5d8f05a"},
|
||||
{file = "librt-0.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8f5d00b49818f4e2b1667db994488b045835e0ac16fe2f924f3871bd2b8ac5"},
|
||||
{file = "librt-0.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c81aef782380f0f13ead670aae01825eb653b44b046aa0e5ebbb79f76ed4aa11"},
|
||||
{file = "librt-0.9.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66b58fed90a545328e80d575467244de3741e088c1af928f0b489ebec3ef3858"},
|
||||
{file = "librt-0.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e78fb7419e07d98c2af4b8567b72b3eaf8cb05caad642e9963465569c8b2d87e"},
|
||||
{file = "librt-0.9.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c3786f0f4490a5cd87f1ed6cefae833ad6b1060d52044ce0434a2e85893afd0"},
|
||||
{file = "librt-0.9.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8494cfc61e03542f2d381e71804990b3931175a29b9278fdb4a5459948778dc2"},
|
||||
{file = "librt-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:07cf11f769831186eeac424376e6189f20ace4f7263e2134bdb9757340d84d4d"},
|
||||
{file = "librt-0.9.0-cp313-cp313-win32.whl", hash = "sha256:850d6d03177e52700af605fd60db7f37dcb89782049a149674d1a9649c2138fd"},
|
||||
{file = "librt-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:a5af136bfba820d592f86c67affcef9b3ff4d4360ac3255e341e964489b48519"},
|
||||
{file = "librt-0.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:4c4d0440a3a8e31d962340c3e1cc3fc9ee7febd34c8d8f770d06adb947779ea5"},
|
||||
{file = "librt-0.9.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:3f05d145df35dca5056a8bc3838e940efebd893a54b3e19b2dda39ceaa299bcb"},
|
||||
{file = "librt-0.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1c587494461ebd42229d0f1739f3aa34237dd9980623ecf1be8d3bcba79f4499"},
|
||||
{file = "librt-0.9.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0a2040f801406b93657a70b72fa12311063a319fee72ce98e1524da7200171f"},
|
||||
{file = "librt-0.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f38bc489037eca88d6ebefc9c4d41a4e07c8e8b4de5188a9e6d290273ad7ebb1"},
|
||||
{file = "librt-0.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3fd278f5e6bf7c75ccd6d12344eb686cc020712683363b66f46ac79d37c799f"},
|
||||
{file = "librt-0.9.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fcbdf2a9ca24e87bbebb47f1fe34e531ef06f104f98c9ccfc953a3f3344c567a"},
|
||||
{file = "librt-0.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e306d956cfa027fe041585f02a1602c32bfa6bb8ebea4899d373383295a6c62f"},
|
||||
{file = "librt-0.9.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:465814ab157986acb9dfa5ccd7df944be5eefc0d08d31ec6e8d88bc71251d845"},
|
||||
{file = "librt-0.9.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:703f4ae36d6240bfe24f542bac784c7e4194ec49c3ba5a994d02891649e2d85b"},
|
||||
{file = "librt-0.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3be322a15ee5e70b93b7a59cfd074614f22cc8c9ff18bd27f474e79137ea8d3b"},
|
||||
{file = "librt-0.9.0-cp314-cp314-win32.whl", hash = "sha256:b8da9f8035bb417770b1e1610526d87ad4fc58a2804dc4d79c53f6d2cf5a6eb9"},
|
||||
{file = "librt-0.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:b8bd70d5d816566a580d193326912f4a76ec2d28a97dc4cd4cc831c0af8e330e"},
|
||||
{file = "librt-0.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:fc5758e2b7a56532dc33e3c544d78cbaa9ecf0a0f2a2da2df882c1d6b99a317f"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f24b90b0e0c8cc9491fb1693ae91fe17cb7963153a1946395acdbdd5818429a4"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fe56e80badb66fdcde06bef81bbaa5bfcf6fbd7aefb86222d9e369c38c6b228"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:527b5b820b47a09e09829051452bb0d1dd2122261254e2a6f674d12f1d793d54"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d429bdd4ac0ab17c8e4a8af0ed2a7440b16eba474909ab357131018fe8c7e71"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7202bdcac47d3a708271c4304a474a8605a4a9a4a709e954bf2d3241140aa938"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0d620e74897f8c2613b3c4e2e9c1e422eb46d2ddd07df540784d44117836af3"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d69fc39e627908f4c03297d5a88d9284b73f4d90b424461e32e8c2485e21c283"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c2640e23d2b7c98796f123ffd95cf2022c7777aa8a4a3b98b36c570d37e85eee"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:451daa98463b7695b0a30aa56bf637831ea559e7b8101ac2ef6382e8eb15e29c"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:928bd06eca2c2bbf4349e5b817f837509b0604342e65a502de1d50a7570afd15"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-win32.whl", hash = "sha256:a9c63e04d003bc0fb6a03b348018b9a3002f98268200e22cc80f146beac5dc40"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f162af66a2ed3f7d1d161a82ca584efd15acd9c1cff190a373458c32f7d42118"},
|
||||
{file = "librt-0.9.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a4b25c6c25cac5d0d9d6d6da855195b254e0021e513e0249f0e3b444dc6e0e61"},
|
||||
{file = "librt-0.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5112c2fb7c2eefefaeaf5c97fec81343ef44ee86a30dcfaa8223822fba6467b4"},
|
||||
{file = "librt-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a81eea9b999b985e4bacc650c4312805ea7008fd5e45e1bf221310176a7bcb3a"},
|
||||
{file = "librt-0.9.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eea1b54943475f51698f85fa230c65ccac769f1e603b981be060ac5763d90927"},
|
||||
{file = "librt-0.9.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81107843ed1836874b46b310f9b1816abcb89912af627868522461c3b7333c0f"},
|
||||
{file = "librt-0.9.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa95738a68cedd3a6f5492feddc513e2e166b50602958139e47bbdd82da0f5a7"},
|
||||
{file = "librt-0.9.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6788207daa0c19955d2b668f3294a368d19f67d9b5f274553fd073c1260cbb9f"},
|
||||
{file = "librt-0.9.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f48c963a76d71b9d7927eb817b543d0dccd52ab6648b99d37bd54f4cd475d856"},
|
||||
{file = "librt-0.9.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:42ff8a962554c350d4a83cf47d9b7b78b0e6ff7943e87df7cdfc97c07f3c016f"},
|
||||
{file = "librt-0.9.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:657f8ba7b9eaaa82759a104137aed2a3ef7bc46ccfd43e0d89b04005b3e0a4cc"},
|
||||
{file = "librt-0.9.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d03fa4fd277a7974c1978c92c374c57f44edeee163d147b477b143446ad1bf6"},
|
||||
{file = "librt-0.9.0-cp39-cp39-win32.whl", hash = "sha256:d9da80e5b04acce03ced8ba6479a71c2a2edf535c2acc0d09c80d2f80f3bad15"},
|
||||
{file = "librt-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:54d412e47c21b85865676ed0724e37a89e9593c2eee1e7367adf85bfad56ffb1"},
|
||||
{file = "librt-0.9.0.tar.gz", hash = "sha256:a0951822531e7aee6e0dfb556b30d5ee36bbe234faf60c20a16c01be3530869d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loguru"
|
||||
version = "0.7.3"
|
||||
description = "Python logging made (stupidly) simple"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.5"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"},
|
||||
{file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
|
||||
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==0.910) ; python_version < \"3.6\"", "mypy (==0.971) ; python_version == \"3.6\"", "mypy (==1.13.0) ; python_version >= \"3.8\"", "mypy (==1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.20.1"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "mypy-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3ba5d1e712ada9c3b6223dcbc5a31dac334ed62991e5caa17bcf5a4ddc349af0"},
|
||||
{file = "mypy-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e731284c117b0987fb1e6c5013a56f33e7faa1fce594066ab83876183ce1c66"},
|
||||
{file = "mypy-1.20.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e945b872a05f4fbefabe2249c0b07b6b194e5e11a86ebee9edf855de09806c"},
|
||||
{file = "mypy-1.20.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fc88acef0dc9b15246502b418980478c1bfc9702057a0e1e7598d01a7af8937"},
|
||||
{file = "mypy-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:14911a115c73608f155f648b978c5055d16ff974e6b1b5512d7fedf4fa8b15c6"},
|
||||
{file = "mypy-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:76d9b4c992cca3331d9793ef197ae360ea44953cf35beb2526e95b9e074f2866"},
|
||||
{file = "mypy-1.20.1-cp310-cp310-win_arm64.whl", hash = "sha256:b408722f80be44845da555671a5ef3a0c63f51ca5752b0c20e992dc9c0fbd3cd"},
|
||||
{file = "mypy-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c01eb9bac2c6a962d00f9d23421cd2913840e65bba365167d057bd0b4171a92e"},
|
||||
{file = "mypy-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55d12ddbd8a9cac5b276878bd534fa39fff5bf543dc6ae18f25d30c8d7d27fca"},
|
||||
{file = "mypy-1.20.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0aa322c1468b6cdfc927a44ce130f79bb44bcd34eb4a009eb9f96571fd80955"},
|
||||
{file = "mypy-1.20.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f8bc95899cf676b6e2285779a08a998cc3a7b26f1026752df9d2741df3c79e8"},
|
||||
{file = "mypy-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:47c2b90191a870a04041e910277494b0d92f0711be9e524d45c074fe60c00b65"},
|
||||
{file = "mypy-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:9857dc8d2ec1a392ffbda518075beb00ac58859979c79f9e6bdcb7277082c2f2"},
|
||||
{file = "mypy-1.20.1-cp311-cp311-win_arm64.whl", hash = "sha256:09d8df92bb25b6065ab91b178da843dda67b33eb819321679a6e98a907ce0e10"},
|
||||
{file = "mypy-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:36ee2b9c6599c230fea89bbd79f401f9f9f8e9fcf0c777827789b19b7da90f51"},
|
||||
{file = "mypy-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fba3fb0968a7b48806b0c90f38d39296f10766885a94c83bd21399de1e14eb28"},
|
||||
{file = "mypy-1.20.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef1415a637cd3627d6304dfbeddbadd21079dafc2a8a753c477ce4fc0c2af54f"},
|
||||
{file = "mypy-1.20.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef3461b1ad5cd446e540016e90b5984657edda39f982f4cc45ca317b628f5a37"},
|
||||
{file = "mypy-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:542dd63c9e1339b6092eb25bd515f3a32a1453aee8c9521d2ddb17dacd840237"},
|
||||
{file = "mypy-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:1d55c7cd8ca22e31f93af2a01160a9e95465b5878de23dba7e48116052f20a8d"},
|
||||
{file = "mypy-1.20.1-cp312-cp312-win_arm64.whl", hash = "sha256:f5b84a79070586e0d353ee07b719d9d0a4aa7c8ee90c0ea97747e98cbe193019"},
|
||||
{file = "mypy-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f3886c03e40afefd327bd70b3f634b39ea82e87f314edaa4d0cce4b927ddcc1"},
|
||||
{file = "mypy-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e860eb3904f9764e83bafd70c8250bdffdc7dde6b82f486e8156348bf7ceb184"},
|
||||
{file = "mypy-1.20.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4b5aac6e785719da51a84f5d09e9e843d473170a9045b1ea7ea1af86225df4b"},
|
||||
{file = "mypy-1.20.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f37b6cd0fe2ad3a20f05ace48ca3523fc52ff86940e34937b439613b6854472e"},
|
||||
{file = "mypy-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4bbb0f6b54ce7cc350ef4a770650d15fa70edd99ad5267e227133eda9c94218"},
|
||||
{file = "mypy-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:c3dc20f8ec76eecd77148cdd2f1542ed496e51e185713bf488a414f862deb8f2"},
|
||||
{file = "mypy-1.20.1-cp313-cp313-win_arm64.whl", hash = "sha256:a9d62bbac5d6d46718e2b0330b25e6264463ed832722b8f7d4440ff1be3ca895"},
|
||||
{file = "mypy-1.20.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:12927b9c0ed794daedcf1dab055b6c613d9d5659ac511e8d936d96f19c087d12"},
|
||||
{file = "mypy-1.20.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:752507dd481e958b2c08fc966d3806c962af5a9433b5bf8f3bdd7175c20e34fe"},
|
||||
{file = "mypy-1.20.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c614655b5a065e56274c6cbbe405f7cf7e96c0654db7ba39bc680238837f7b08"},
|
||||
{file = "mypy-1.20.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c3f6221a76f34d5100c6d35b3ef6b947054123c3f8d6938a4ba00b1308aa572"},
|
||||
{file = "mypy-1.20.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4bdfc06303ac06500af71ea0cdbe995c502b3c9ba32f3f8313523c137a25d1b6"},
|
||||
{file = "mypy-1.20.1-cp314-cp314-win_amd64.whl", hash = "sha256:0131edd7eba289973d1ba1003d1a37c426b85cdef76650cd02da6420898a5eb3"},
|
||||
{file = "mypy-1.20.1-cp314-cp314-win_arm64.whl", hash = "sha256:33f02904feb2c07e1fdf7909026206396c9deeb9e6f34d466b4cfedb0aadbbe4"},
|
||||
{file = "mypy-1.20.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:168472149dd8cc505c98cefd21ad77e4257ed6022cd5ed2fe2999bed56977a5a"},
|
||||
{file = "mypy-1.20.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eb674600309a8f22790cca883a97c90299f948183ebb210fbef6bcee07cb1986"},
|
||||
{file = "mypy-1.20.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef2b2e4cc464ba9795459f2586923abd58a0055487cbe558cb538ea6e6bc142a"},
|
||||
{file = "mypy-1.20.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee461d396dd46b3f0ed5a098dbc9b8860c81c46ad44fa071afcfbc149f167c9"},
|
||||
{file = "mypy-1.20.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e364926308b3e66f1361f81a566fc1b2f8cd47fc8525e8136d4058a65a4b4f02"},
|
||||
{file = "mypy-1.20.1-cp314-cp314t-win_amd64.whl", hash = "sha256:a0c17fbd746d38c70cbc42647cfd884f845a9708a4b160a8b4f7e70d41f4d7fa"},
|
||||
{file = "mypy-1.20.1-cp314-cp314t-win_arm64.whl", hash = "sha256:db2cb89654626a912efda69c0d5c1d22d948265e2069010d3dde3abf751c7d08"},
|
||||
{file = "mypy-1.20.1-py3-none-any.whl", hash = "sha256:1aae28507f253fe82d883790d1c0a0d35798a810117c88184097fe8881052f06"},
|
||||
{file = "mypy-1.20.1.tar.gz", hash = "sha256:6fc3f4ecd52de81648fed1945498bf42fa2993ddfad67c9056df36ae5757f804"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
librt = {version = ">=0.8.0", markers = "platform_python_implementation != \"PyPy\""}
|
||||
mypy_extensions = ">=1.0.0"
|
||||
pathspec = ">=1.0.0"
|
||||
typing_extensions = ">=4.6.0"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
faster-cache = ["orjson"]
|
||||
install-types = ["pip"]
|
||||
mypyc = ["setuptools (>=50)"]
|
||||
native-parser = ["ast-serialize (>=0.1.1,<1.0.0)"]
|
||||
reports = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.1.0"
|
||||
description = "Type system extensions for programs checked with the mypy type checker."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
|
||||
{file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "26.1"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f"},
|
||||
{file = "packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "1.0.4"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"},
|
||||
{file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
hyperscan = ["hyperscan (>=0.7)"]
|
||||
optional = ["typing-extensions (>=4)"]
|
||||
re2 = ["google-re2 (>=1.1)"]
|
||||
tests = ["pytest (>=9)", "typing-extensions (>=4.15)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
|
||||
{file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["coverage", "pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.20.0"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"},
|
||||
{file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.3"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"},
|
||||
{file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
|
||||
iniconfig = ">=1.0.1"
|
||||
packaging = ">=22"
|
||||
pluggy = ">=1.5,<2"
|
||||
pygments = ">=2.7.2"
|
||||
|
||||
[package.extras]
|
||||
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.15.10"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f"},
|
||||
{file = "ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e"},
|
||||
{file = "ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1"},
|
||||
{file = "ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e"},
|
||||
{file = "ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1"},
|
||||
{file = "ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef"},
|
||||
{file = "ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158"},
|
||||
{file = "ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0"},
|
||||
{file = "ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609"},
|
||||
{file = "ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f"},
|
||||
{file = "ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151"},
|
||||
{file = "ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8"},
|
||||
{file = "ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07"},
|
||||
{file = "ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48"},
|
||||
{file = "ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5"},
|
||||
{file = "ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed"},
|
||||
{file = "ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188"},
|
||||
{file = "ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.9+"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
|
||||
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "win32-setctime"
|
||||
version = "1.2.0"
|
||||
description = "A small Python utility to set file creation time on Windows"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
groups = ["main"]
|
||||
markers = "sys_platform == \"win32\""
|
||||
files = [
|
||||
{file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"},
|
||||
{file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.14,<4.0"
|
||||
content-hash = "23e204d0d68650008db039ff23151ebaa1d69730287ca59d2c0eea7c9edbd67d"
|
||||
26
pyproject.toml
Normal file
26
pyproject.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[project]
|
||||
name = "planetarytime"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = [
|
||||
{name = "Jan Doubravský",email = "jan.doubravsky@gmail.com"}
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.14,<4.0"
|
||||
dependencies = [
|
||||
"loguru (>=0.7.3,<0.8.0)"
|
||||
]
|
||||
|
||||
[tool.poetry]
|
||||
packages = [{include = "planetarytime", from = "src"}]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest (>=9.0.3,<10.0.0)",
|
||||
"mypy (>=1.20.1,<2.0.0)",
|
||||
"ruff (>=0.15.10,<0.16.0)"
|
||||
]
|
||||
13
src/planetarytime/__init__.py
Normal file
13
src/planetarytime/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from planetarytime.body import Body
|
||||
from planetarytime.epoch import EpochType
|
||||
from planetarytime.exceptions import DatetimePrecedesEpochError, EpochUnavailableError, PlanetaryTimeError
|
||||
from planetarytime.planetary_time import PlanetaryTime
|
||||
|
||||
__all__ = [
|
||||
"Body",
|
||||
"EpochType",
|
||||
"PlanetaryTime",
|
||||
"PlanetaryTimeError",
|
||||
"EpochUnavailableError",
|
||||
"DatetimePrecedesEpochError",
|
||||
]
|
||||
BIN
src/planetarytime/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
src/planetarytime/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
src/planetarytime/__pycache__/body.cpython-314.pyc
Normal file
BIN
src/planetarytime/__pycache__/body.cpython-314.pyc
Normal file
Binary file not shown.
BIN
src/planetarytime/__pycache__/epoch.cpython-314.pyc
Normal file
BIN
src/planetarytime/__pycache__/epoch.cpython-314.pyc
Normal file
Binary file not shown.
BIN
src/planetarytime/__pycache__/exceptions.cpython-314.pyc
Normal file
BIN
src/planetarytime/__pycache__/exceptions.cpython-314.pyc
Normal file
Binary file not shown.
BIN
src/planetarytime/__pycache__/planetary_time.cpython-314.pyc
Normal file
BIN
src/planetarytime/__pycache__/planetary_time.cpython-314.pyc
Normal file
Binary file not shown.
55
src/planetarytime/body.py
Normal file
55
src/planetarytime/body.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Body(Enum):
|
||||
"""Planetary body in the Solar System."""
|
||||
|
||||
MERCURY = "Mercury"
|
||||
VENUS = "Venus"
|
||||
MARS = "Mars"
|
||||
JUPITER = "Jupiter"
|
||||
SATURN = "Saturn"
|
||||
URANUS = "Uranus"
|
||||
NEPTUNE = "Neptune"
|
||||
|
||||
@property
|
||||
def rotation_hours(self) -> float:
|
||||
"""Sidereal rotation period in Earth hours."""
|
||||
return _ROTATION_HOURS[self]
|
||||
|
||||
@property
|
||||
def orbital_hours(self) -> float:
|
||||
"""Orbital period around the Sun in Earth hours."""
|
||||
return _ORBITAL_HOURS[self]
|
||||
|
||||
@property
|
||||
def hours_per_sol(self) -> int:
|
||||
"""Number of hours in one sol (rotation period rounded to nearest hour)."""
|
||||
return round(self.rotation_hours)
|
||||
|
||||
@property
|
||||
def sols_per_year(self) -> int:
|
||||
"""Number of sols in one planetary year (orbital period / rotation period, rounded)."""
|
||||
return round(self.orbital_hours / self.rotation_hours)
|
||||
|
||||
|
||||
_ROTATION_HOURS: dict[Body, float] = {
|
||||
Body.MERCURY: 1407.6,
|
||||
Body.VENUS: 5832.5,
|
||||
Body.MARS: 24.6,
|
||||
Body.JUPITER: 9.9,
|
||||
Body.SATURN: 10.7,
|
||||
Body.URANUS: 17.2,
|
||||
Body.NEPTUNE: 16.1,
|
||||
}
|
||||
|
||||
# Orbital periods in Earth hours
|
||||
_ORBITAL_HOURS: dict[Body, float] = {
|
||||
Body.MERCURY: 87.97 * 24,
|
||||
Body.VENUS: 224.70 * 24,
|
||||
Body.MARS: 686.97 * 24,
|
||||
Body.JUPITER: 4332.59 * 24,
|
||||
Body.SATURN: 10759.22 * 24,
|
||||
Body.URANUS: 30688.50 * 24,
|
||||
Body.NEPTUNE: 60182.00 * 24,
|
||||
}
|
||||
51
src/planetarytime/epoch.py
Normal file
51
src/planetarytime/epoch.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
|
||||
from planetarytime.body import Body
|
||||
from planetarytime.exceptions import EpochUnavailableError
|
||||
|
||||
|
||||
class EpochType(Enum):
|
||||
"""How the starting day is determined."""
|
||||
|
||||
DISCOVERY = "discovery"
|
||||
CONTACT = "contact"
|
||||
|
||||
|
||||
# Discovery dates for Solar System bodies (UTC midnight).
|
||||
_DISCOVERY_DATES: dict[Body, datetime] = {
|
||||
Body.MERCURY: datetime(1631, 11, 7, tzinfo=timezone.utc), # first recorded transit (Gassendi)
|
||||
Body.VENUS: datetime(1610, 1, 1, tzinfo=timezone.utc), # telescopic observation (Galileo)
|
||||
Body.MARS: datetime(1610, 1, 1, tzinfo=timezone.utc), # telescopic observation (Galileo)
|
||||
Body.JUPITER: datetime(1610, 1, 7, tzinfo=timezone.utc), # moons discovered (Galileo)
|
||||
Body.SATURN: datetime(1610, 7, 25, tzinfo=timezone.utc), # rings observed (Galileo)
|
||||
Body.URANUS: datetime(1781, 3, 13, tzinfo=timezone.utc), # Herschel
|
||||
Body.NEPTUNE: datetime(1846, 9, 23, tzinfo=timezone.utc), # Le Verrier / Galle
|
||||
}
|
||||
|
||||
# First contact dates — automated probe landing or crewed landing.
|
||||
# None means no contact has occurred yet.
|
||||
_CONTACT_DATES: dict[Body, datetime | None] = {
|
||||
Body.MERCURY: datetime(2011, 3, 18, tzinfo=timezone.utc), # MESSENGER orbit insertion (closest approach)
|
||||
Body.VENUS: datetime(1970, 12, 15, tzinfo=timezone.utc), # Venera 7 — first soft landing
|
||||
Body.MARS: datetime(1976, 7, 20, tzinfo=timezone.utc), # Viking 1 — first soft landing
|
||||
Body.JUPITER: None,
|
||||
Body.SATURN: None,
|
||||
Body.URANUS: None,
|
||||
Body.NEPTUNE: None,
|
||||
}
|
||||
|
||||
|
||||
def get_epoch_date(body: Body, epoch_type: EpochType) -> datetime:
|
||||
"""Return the epoch datetime for a given body and epoch type.
|
||||
|
||||
Raises:
|
||||
ValueError: if contact epoch is requested but no contact has occurred.
|
||||
"""
|
||||
if epoch_type is EpochType.DISCOVERY:
|
||||
return _DISCOVERY_DATES[body]
|
||||
|
||||
contact = _CONTACT_DATES[body]
|
||||
if contact is None:
|
||||
raise EpochUnavailableError(f"No contact with {body.value} has occurred — contact epoch is unavailable.")
|
||||
return contact
|
||||
10
src/planetarytime/exceptions.py
Normal file
10
src/planetarytime/exceptions.py
Normal file
@@ -0,0 +1,10 @@
|
||||
class PlanetaryTimeError(Exception):
|
||||
"""Base exception for all planetarytime errors."""
|
||||
|
||||
|
||||
class EpochUnavailableError(PlanetaryTimeError):
|
||||
"""Raised when a requested epoch has not occurred yet for a given body."""
|
||||
|
||||
|
||||
class DatetimePrecedesEpochError(PlanetaryTimeError):
|
||||
"""Raised when the provided datetime is earlier than the body's epoch."""
|
||||
157
src/planetarytime/planetary_time.py
Normal file
157
src/planetarytime/planetary_time.py
Normal file
@@ -0,0 +1,157 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from planetarytime.body import Body
|
||||
from planetarytime.epoch import EpochType, get_epoch_date
|
||||
from planetarytime.exceptions import DatetimePrecedesEpochError
|
||||
|
||||
|
||||
class PlanetaryTime:
|
||||
"""Represents a point in time expressed on a specific planetary body.
|
||||
|
||||
Time is anchored to an epoch (discovery or first contact). The clock is
|
||||
scaled so that one planetary hour is as close to one Earth hour as possible:
|
||||
- hours_per_sol = rotation period rounded to nearest Earth hour
|
||||
- sols_per_year = orbital period / rotation period, rounded
|
||||
|
||||
The unit "sol" is used for all non-Earth bodies instead of "day".
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
body: Body,
|
||||
epoch_type: EpochType,
|
||||
total_seconds_since_epoch: float,
|
||||
) -> None:
|
||||
self._body = body
|
||||
self._epoch_type = epoch_type
|
||||
self._total_seconds = total_seconds_since_epoch
|
||||
|
||||
seconds_per_hour: float = 3600.0
|
||||
seconds_per_sol: float = body.hours_per_sol * seconds_per_hour
|
||||
seconds_per_year: float = body.sols_per_year * seconds_per_sol
|
||||
|
||||
total_seconds = max(0.0, total_seconds_since_epoch)
|
||||
|
||||
self._year: int = int(total_seconds // seconds_per_year)
|
||||
remainder = total_seconds % seconds_per_year
|
||||
|
||||
self._sol: int = int(remainder // seconds_per_sol)
|
||||
remainder %= seconds_per_sol
|
||||
|
||||
self._hour: int = int(remainder // seconds_per_hour)
|
||||
remainder %= seconds_per_hour
|
||||
|
||||
self._minute: int = int(remainder // 60)
|
||||
self._second: int = int(remainder % 60)
|
||||
|
||||
logger.debug(
|
||||
"PlanetaryTime constructed: body={} epoch={} year={} sol={} {:02d}:{:02d}:{:02d}",
|
||||
body.value,
|
||||
epoch_type.value,
|
||||
self._year,
|
||||
self._sol,
|
||||
self._hour,
|
||||
self._minute,
|
||||
self._second,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Factory
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@classmethod
|
||||
def from_earth(
|
||||
cls,
|
||||
earth_dt: datetime,
|
||||
body: Body,
|
||||
epoch_type: EpochType = EpochType.DISCOVERY,
|
||||
) -> PlanetaryTime:
|
||||
"""Create a PlanetaryTime from an Earth datetime.
|
||||
|
||||
Args:
|
||||
earth_dt: Earth datetime (timezone-aware or naive UTC).
|
||||
body: Target planetary body.
|
||||
epoch_type: Which epoch to use as year 0, sol 0.
|
||||
|
||||
Returns:
|
||||
PlanetaryTime instance for the given moment on the target body.
|
||||
|
||||
Raises:
|
||||
DatetimePrecedesEpochError: if the datetime precedes the epoch.
|
||||
EpochUnavailableError: if contact epoch is requested but no contact has occurred.
|
||||
"""
|
||||
if earth_dt.tzinfo is None:
|
||||
earth_dt = earth_dt.replace(tzinfo=timezone.utc)
|
||||
|
||||
epoch_dt = get_epoch_date(body, epoch_type)
|
||||
delta = earth_dt - epoch_dt
|
||||
total_seconds = delta.total_seconds()
|
||||
|
||||
logger.info("Converting Earth datetime {} to {} time (epoch={})", earth_dt, body.value, epoch_type.value)
|
||||
|
||||
if total_seconds < 0:
|
||||
raise DatetimePrecedesEpochError(
|
||||
f"Earth datetime {earth_dt} precedes the {epoch_type.value} epoch "
|
||||
f"for {body.value} ({epoch_dt})."
|
||||
)
|
||||
|
||||
return cls(body, epoch_type, total_seconds)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Properties
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def body(self) -> Body:
|
||||
return self._body
|
||||
|
||||
@property
|
||||
def epoch_type(self) -> EpochType:
|
||||
return self._epoch_type
|
||||
|
||||
@property
|
||||
def year(self) -> int:
|
||||
"""Year number since epoch (0-indexed)."""
|
||||
return self._year
|
||||
|
||||
@property
|
||||
def sol(self) -> int:
|
||||
"""Sol number within the current year (0-indexed)."""
|
||||
return self._sol
|
||||
|
||||
@property
|
||||
def hour(self) -> int:
|
||||
"""Hour within the current sol (0-indexed)."""
|
||||
return self._hour
|
||||
|
||||
@property
|
||||
def minute(self) -> int:
|
||||
"""Minute within the current hour (0-indexed)."""
|
||||
return self._minute
|
||||
|
||||
@property
|
||||
def second(self) -> int:
|
||||
"""Second within the current minute (0-indexed)."""
|
||||
return self._second
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# String representation
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"PlanetaryTime(body={self._body.value!r}, epoch={self._epoch_type.value!r}, "
|
||||
f"year={self._year}, sol={self._sol}, "
|
||||
f"time={self._hour:02d}:{self._minute:02d}:{self._second:02d})"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f"Year {self._year}, Sol {self._sol}, "
|
||||
f"{self._hour:02d}:{self._minute:02d}:{self._second:02d} "
|
||||
f"({self._body.value} / {self._epoch_type.value} epoch)"
|
||||
)
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
BIN
tests/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
tests/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
tests/__pycache__/test_body.cpython-314-pytest-9.0.3.pyc
Normal file
BIN
tests/__pycache__/test_body.cpython-314-pytest-9.0.3.pyc
Normal file
Binary file not shown.
Binary file not shown.
33
tests/test_body.py
Normal file
33
tests/test_body.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from planetarytime import Body
|
||||
|
||||
|
||||
def test_mars_hours_per_sol() -> None:
|
||||
assert Body.MARS.hours_per_sol == 25
|
||||
|
||||
|
||||
def test_jupiter_hours_per_sol() -> None:
|
||||
assert Body.JUPITER.hours_per_sol == 10
|
||||
|
||||
|
||||
def test_hours_per_sol_equals_rounded_rotation() -> None:
|
||||
for body in Body:
|
||||
assert body.hours_per_sol == round(body.rotation_hours)
|
||||
|
||||
|
||||
def test_all_bodies_have_positive_rotation() -> None:
|
||||
for body in Body:
|
||||
assert body.rotation_hours > 0
|
||||
|
||||
|
||||
def test_mars_sols_per_year() -> None:
|
||||
assert Body.MARS.sols_per_year == 670
|
||||
|
||||
|
||||
def test_all_bodies_have_positive_sols_per_year() -> None:
|
||||
for body in Body:
|
||||
assert body.sols_per_year > 0
|
||||
|
||||
|
||||
def test_sols_per_year_derived_from_orbital_and_rotation() -> None:
|
||||
for body in Body:
|
||||
assert body.sols_per_year == round(body.orbital_hours / body.rotation_hours)
|
||||
82
tests/test_planetary_time.py
Normal file
82
tests/test_planetary_time.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import pytest
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
from planetarytime import Body, EpochType, PlanetaryTime
|
||||
from planetarytime.exceptions import DatetimePrecedesEpochError, EpochUnavailableError
|
||||
from planetarytime.epoch import get_epoch_date
|
||||
|
||||
|
||||
def test_from_earth_at_epoch_is_year_zero_sol_zero() -> None:
|
||||
epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY)
|
||||
pt = PlanetaryTime.from_earth(epoch_dt, Body.MARS, EpochType.DISCOVERY)
|
||||
assert pt.year == 0
|
||||
assert pt.sol == 0
|
||||
assert pt.hour == 0
|
||||
assert pt.minute == 0
|
||||
assert pt.second == 0
|
||||
|
||||
|
||||
def test_from_earth_one_sol_later() -> None:
|
||||
epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY)
|
||||
one_sol_later = epoch_dt + timedelta(hours=Body.MARS.hours_per_sol)
|
||||
pt = PlanetaryTime.from_earth(one_sol_later, Body.MARS, EpochType.DISCOVERY)
|
||||
assert pt.year == 0
|
||||
assert pt.sol == 1
|
||||
assert pt.hour == 0
|
||||
|
||||
|
||||
def test_from_earth_one_year_later() -> None:
|
||||
epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY)
|
||||
one_year_seconds = Body.MARS.sols_per_year * Body.MARS.hours_per_sol * 3600
|
||||
one_year_later = epoch_dt + timedelta(seconds=one_year_seconds)
|
||||
pt = PlanetaryTime.from_earth(one_year_later, Body.MARS, EpochType.DISCOVERY)
|
||||
assert pt.year == 1
|
||||
assert pt.sol == 0
|
||||
assert pt.hour == 0
|
||||
|
||||
|
||||
def test_from_earth_one_hour_later() -> None:
|
||||
epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY)
|
||||
one_hour_later = epoch_dt + timedelta(hours=1)
|
||||
pt = PlanetaryTime.from_earth(one_hour_later, Body.MARS, EpochType.DISCOVERY)
|
||||
assert pt.year == 0
|
||||
assert pt.sol == 0
|
||||
assert pt.hour == 1
|
||||
assert pt.minute == 0
|
||||
|
||||
|
||||
def test_from_earth_naive_datetime_treated_as_utc() -> None:
|
||||
epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY)
|
||||
naive = epoch_dt.replace(tzinfo=None)
|
||||
pt = PlanetaryTime.from_earth(naive, Body.MARS, EpochType.DISCOVERY)
|
||||
assert pt.year == 0
|
||||
assert pt.sol == 0
|
||||
|
||||
|
||||
def test_from_earth_before_epoch_raises() -> None:
|
||||
epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY)
|
||||
before_epoch = epoch_dt - timedelta(days=1)
|
||||
with pytest.raises(DatetimePrecedesEpochError):
|
||||
PlanetaryTime.from_earth(before_epoch, Body.MARS, EpochType.DISCOVERY)
|
||||
|
||||
|
||||
def test_contact_epoch_unavailable_raises() -> None:
|
||||
with pytest.raises(EpochUnavailableError):
|
||||
PlanetaryTime.from_earth(datetime(2024, 1, 1, tzinfo=timezone.utc), Body.JUPITER, EpochType.CONTACT)
|
||||
|
||||
|
||||
def test_str_contains_body_name_and_sol() -> None:
|
||||
epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY)
|
||||
pt = PlanetaryTime.from_earth(epoch_dt, Body.MARS, EpochType.DISCOVERY)
|
||||
assert "Mars" in str(pt)
|
||||
assert "Sol" in str(pt)
|
||||
assert "Year" in str(pt)
|
||||
|
||||
|
||||
def test_repr_contains_year_and_sol() -> None:
|
||||
epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY)
|
||||
pt = PlanetaryTime.from_earth(epoch_dt, Body.MARS, EpochType.DISCOVERY)
|
||||
r = repr(pt)
|
||||
assert "PlanetaryTime(" in r
|
||||
assert "year=" in r
|
||||
assert "sol=" in r
|
||||
7
zkouska.py
Normal file
7
zkouska.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import planetarytime as pt
|
||||
import datetime
|
||||
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
mars = pt.PlanetaryTime.from_earth(now, pt.Body.VENUS)
|
||||
|
||||
print(mars)
|
||||
Reference in New Issue
Block a user