update
This commit is contained in:
commit
7ff887027e
256
.gitignore
vendored
Normal file
256
.gitignore
vendored
Normal file
@ -0,0 +1,256 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/windows,linux,python,visualstudiocode,vim
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=windows,linux,python,visualstudiocode,vim
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
### Python Patch ###
|
||||
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
||||
poetry.toml
|
||||
|
||||
# ruff
|
||||
.ruff_cache/
|
||||
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
### Vim ###
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
!*.svg # comment out if you don't need vector files
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
Sessionx.vim
|
||||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
# Auto-generated tag files
|
||||
tags
|
||||
# Persistent undo
|
||||
[._]*.un~
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/windows,linux,python,visualstudiocode,vim
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
||||
3.11
|
||||
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"jalali",
|
||||
"jdatetime",
|
||||
"reza",
|
||||
"setuptools"
|
||||
]
|
||||
}
|
||||
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Python Reza Logging
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
setup_logging(
|
||||
calendar="jalali",
|
||||
include_app_name=False,
|
||||
include_name=True,
|
||||
include_pid=False,
|
||||
include_thread=False,
|
||||
console_level=logging.INFO,
|
||||
use_rich=True,
|
||||
time_zone_name="Asia/Tehran",
|
||||
)
|
||||
logging.getLogger("elasticsearch").setLevel(logging.WARNING)
|
||||
logging.getLogger("elastic_transport").setLevel(logging.WARNING)
|
||||
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
||||
logging.getLogger("tortoise.db_client").setLevel(logging.WARNING)
|
||||
logging.getLogger("asyncio").setLevel(logging.WARNING)
|
||||
logging.getLogger("tortoise").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
log = logging.getLogger(__name__)
|
||||
log.info("script_started")
|
||||
```
|
||||
21
pyproject.toml
Normal file
21
pyproject.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[project]
|
||||
name = "py-reza-logging"
|
||||
version = "0.1.0"
|
||||
description = "Reusable logging setup utilities"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=68", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = {"" = "src"}
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
rich = ["rich>=14.3.2"]
|
||||
jalali = ["jdatetime>=5.2.0"]
|
||||
3
src/py_reza_logging/__init__.py
Normal file
3
src/py_reza_logging/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .setup import setup_logging
|
||||
|
||||
__all__ = ["setup_logging"]
|
||||
332
src/py_reza_logging/setup.py
Normal file
332
src/py_reza_logging/setup.py
Normal file
@ -0,0 +1,332 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
|
||||
def setup_logging(
|
||||
*,
|
||||
app_name: str | None = "script",
|
||||
log_dir: str | Path = "logs",
|
||||
console_level: int | None = None,
|
||||
file_level: int | None = None,
|
||||
root_level: int | None = None,
|
||||
calendar: str | None = None, # "gregorian" | "jalali" | None
|
||||
json_logs: bool | None = None,
|
||||
enable_file: bool | None = None, # None -> auto based on docker + env overrides
|
||||
max_bytes: int = 10 * 1024 * 1024,
|
||||
backup_count: int = 10,
|
||||
use_rich: bool = False,
|
||||
include_app_name: bool = True,
|
||||
include_name: bool = True,
|
||||
microseconds: int = 3, # 0, 3, or 6
|
||||
include_pid: bool = True,
|
||||
include_thread: bool = True,
|
||||
time_zone_name: str = "UTC",
|
||||
) -> None:
|
||||
"""
|
||||
Script-friendly logging:
|
||||
- Console always
|
||||
- File only if enabled (default: enabled locally, disabled in Docker)
|
||||
- Optional JSON logs
|
||||
|
||||
Formatter options:
|
||||
- microseconds: 0 (none), 3 (milliseconds), 6 (microseconds)
|
||||
- app_name: if None, it's omitted
|
||||
- include_pid/include_thread toggles
|
||||
"""
|
||||
|
||||
if use_rich:
|
||||
try:
|
||||
from rich.logging import RichHandler
|
||||
except Exception:
|
||||
use_rich = False
|
||||
|
||||
if use_rich and not sys.stderr.isatty():
|
||||
use_rich = False
|
||||
|
||||
# ---- Environment overrides ----
|
||||
env_root = os.getenv("LOG_LEVEL")
|
||||
env_console = os.getenv("LOG_LEVEL_CONSOLE")
|
||||
env_file = os.getenv("LOG_LEVEL_FILE")
|
||||
|
||||
if root_level is None:
|
||||
root_level = _level_from_env(env_root, default=logging.DEBUG)
|
||||
if console_level is None:
|
||||
console_level = _level_from_env(env_console, default=logging.INFO)
|
||||
if file_level is None:
|
||||
file_level = _level_from_env(env_file, default=logging.DEBUG)
|
||||
|
||||
if json_logs is None:
|
||||
json_logs = _truthy_env(os.getenv("LOG_JSON"), default=False)
|
||||
|
||||
if enable_file is None:
|
||||
env_log_to_file = os.getenv("LOG_TO_FILE")
|
||||
if env_log_to_file is not None:
|
||||
enable_file = _truthy_env(env_log_to_file, default=True)
|
||||
else:
|
||||
enable_file = not _looks_like_docker()
|
||||
|
||||
log_dir = Path(os.getenv("LOG_DIR", str(log_dir))).resolve()
|
||||
|
||||
# ---- Calendar selection ----
|
||||
if calendar is None:
|
||||
calendar = "gregorian"
|
||||
calendar = calendar.strip().lower()
|
||||
|
||||
if calendar == "jalali":
|
||||
try:
|
||||
import jdatetime # noqa: F401
|
||||
except Exception:
|
||||
calendar = "gregorian"
|
||||
|
||||
if calendar not in ("gregorian", "jalali"):
|
||||
raise ValueError("calendar must be 'gregorian' or 'jalali'")
|
||||
|
||||
# ---- Validate formatter knobs ----
|
||||
if microseconds not in (0, 3, 6):
|
||||
raise ValueError("microseconds must be 0, 3, or 6")
|
||||
|
||||
# ---- Configure root ----
|
||||
root = logging.getLogger()
|
||||
root.setLevel(root_level)
|
||||
|
||||
for h in list(root.handlers):
|
||||
root.removeHandler(h)
|
||||
|
||||
# ---- Formatter ----
|
||||
if json_logs:
|
||||
formatter: logging.Formatter = _JsonFormatter(
|
||||
app_name=app_name,
|
||||
calendar=calendar,
|
||||
microseconds=microseconds,
|
||||
include_app_name=include_app_name,
|
||||
include_name=include_name,
|
||||
include_pid=include_pid,
|
||||
include_thread=include_thread,
|
||||
time_zone_name=time_zone_name,
|
||||
)
|
||||
else:
|
||||
formatter = _TextFormatter(
|
||||
app_name=app_name,
|
||||
calendar=calendar,
|
||||
microseconds=microseconds,
|
||||
include_app_name=include_app_name,
|
||||
include_name=include_name,
|
||||
include_pid=include_pid,
|
||||
include_thread=include_thread,
|
||||
time_zone_name=time_zone_name,
|
||||
)
|
||||
|
||||
# ---- Console handler ----
|
||||
if use_rich and not json_logs:
|
||||
console = RichHandler(
|
||||
level=console_level,
|
||||
rich_tracebacks=False,
|
||||
tracebacks_show_locals=False,
|
||||
show_time=False, # YOU already handle time
|
||||
show_level=False, # YOU already handle level
|
||||
show_path=False, # avoid noise
|
||||
markup=False,
|
||||
)
|
||||
console.setFormatter(formatter)
|
||||
else:
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(console_level)
|
||||
console.setFormatter(formatter)
|
||||
|
||||
root.addHandler(console)
|
||||
|
||||
# ---- File handler (rotating) ----
|
||||
if enable_file:
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
log_path = log_dir / f"{app_name or 'app'}.log"
|
||||
|
||||
file_handler = logging.handlers.RotatingFileHandler(
|
||||
filename=log_path,
|
||||
maxBytes=max_bytes,
|
||||
backupCount=backup_count,
|
||||
encoding="utf-8",
|
||||
delay=True,
|
||||
)
|
||||
file_handler.setLevel(file_level)
|
||||
file_handler.setFormatter(formatter)
|
||||
root.addHandler(file_handler)
|
||||
|
||||
|
||||
def _looks_like_docker() -> bool:
|
||||
if os.path.exists("/.dockerenv"):
|
||||
return True
|
||||
try:
|
||||
cgroup = Path("/proc/1/cgroup")
|
||||
if cgroup.exists():
|
||||
txt = cgroup.read_text(errors="ignore")
|
||||
if "docker" in txt or "kubepods" in txt or "containerd" in txt:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def _truthy_env(val: str | None, *, default: bool) -> bool:
|
||||
if val is None:
|
||||
return default
|
||||
return val.strip().lower() in ("1", "true", "yes", "y", "on")
|
||||
|
||||
|
||||
def _level_from_env(val: str | None, *, default: int) -> int:
|
||||
if not val:
|
||||
return default
|
||||
name = val.strip().upper()
|
||||
return getattr(logging, name, default)
|
||||
|
||||
|
||||
def _format_timestamp(*, calendar: str, microseconds: int, time_zone_name: str) -> str:
|
||||
"""
|
||||
microseconds:
|
||||
- 0: YYYY-mm-dd HH:MM:SS
|
||||
- 3: YYYY-mm-dd HH:MM:SS.mmm
|
||||
- 6: YYYY-mm-dd HH:MM:SS.ffffff
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
if calendar == "gregorian":
|
||||
now = datetime.now(tz=ZoneInfo(time_zone_name))
|
||||
base = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if microseconds == 0:
|
||||
return base
|
||||
us = now.microsecond # 0..999999
|
||||
if microseconds == 3:
|
||||
return f"{base}.{us // 1000:03d}"
|
||||
return f"{base}.{us:06d}"
|
||||
|
||||
# Jalali
|
||||
try:
|
||||
import jdatetime
|
||||
except Exception:
|
||||
# fallback
|
||||
now = datetime.now(tz=ZoneInfo(time_zone_name))
|
||||
base = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if microseconds == 0:
|
||||
return base + " (gregorian-fallback)"
|
||||
us = now.microsecond
|
||||
if microseconds == 3:
|
||||
return f"{base}.{us // 1000:03d} (gregorian-fallback)"
|
||||
return f"{base}.{us:06d} (gregorian-fallback)"
|
||||
|
||||
jnow = jdatetime.datetime.now(tz=ZoneInfo(time_zone_name))
|
||||
base = jnow.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if microseconds == 0:
|
||||
return base
|
||||
us = jnow.microsecond
|
||||
if microseconds == 3:
|
||||
return f"{base}.{us // 1000:03d}"
|
||||
return f"{base}.{us:06d}"
|
||||
|
||||
|
||||
def _build_context_suffix(record: logging.LogRecord, *, include_pid: bool, include_thread: bool) -> str:
|
||||
parts: list[str] = []
|
||||
if include_pid:
|
||||
parts.append(str(record.process))
|
||||
if include_thread:
|
||||
parts.append(record.threadName)
|
||||
if not parts:
|
||||
return ""
|
||||
return " [" + ":".join(parts) + "]"
|
||||
|
||||
|
||||
class _TextFormatter(logging.Formatter):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
app_name: str | None,
|
||||
calendar: str,
|
||||
microseconds: int,
|
||||
include_app_name: bool,
|
||||
include_name: bool,
|
||||
include_pid: bool,
|
||||
include_thread: bool,
|
||||
time_zone_name: str,
|
||||
):
|
||||
super().__init__()
|
||||
self.app_name = app_name
|
||||
self.calendar = calendar
|
||||
self.microseconds = microseconds
|
||||
self.include_app_name = include_app_name
|
||||
self.include_name = include_name
|
||||
self.include_pid = include_pid
|
||||
self.include_thread = include_thread
|
||||
self.time_zone_name = time_zone_name
|
||||
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
ts = _format_timestamp(calendar=self.calendar, microseconds=self.microseconds, time_zone_name=self.time_zone_name)
|
||||
|
||||
# best practice: keep a stable, grep-friendly prefix
|
||||
prefix_parts = [ts, record.levelname]
|
||||
|
||||
if self.include_app_name:
|
||||
prefix_parts.append(self.app_name)
|
||||
|
||||
if self.include_name:
|
||||
prefix_parts.append(record.name)
|
||||
|
||||
ctx = _build_context_suffix(record, include_pid=self.include_pid, include_thread=self.include_thread)
|
||||
|
||||
base = " ".join(prefix_parts) + ctx + " " + record.getMessage()
|
||||
|
||||
if record.exc_info:
|
||||
base += "\n" + self.formatException(record.exc_info)
|
||||
|
||||
return base
|
||||
|
||||
|
||||
class _JsonFormatter(logging.Formatter):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
app_name: str | None,
|
||||
calendar: str,
|
||||
microseconds: int,
|
||||
include_app_name: bool,
|
||||
include_name: bool,
|
||||
include_pid: bool,
|
||||
include_thread: bool,
|
||||
time_zone_name: str,
|
||||
):
|
||||
super().__init__()
|
||||
self.app_name = app_name
|
||||
self.calendar = calendar
|
||||
self.microseconds = microseconds
|
||||
self.include_app_name = include_app_name
|
||||
self.include_name = include_name
|
||||
self.include_pid = include_pid
|
||||
self.include_thread = include_thread
|
||||
self.time_zone_name = time_zone_name
|
||||
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
import json
|
||||
|
||||
payload: dict[str, object] = {
|
||||
"ts": _format_timestamp(
|
||||
calendar=self.calendar, microseconds=self.microseconds, time_zone_name=self.time_zone_name
|
||||
),
|
||||
"level": record.levelname,
|
||||
"msg": record.getMessage(),
|
||||
}
|
||||
|
||||
if self.include_app_name:
|
||||
payload["app"] = self.app_name
|
||||
if self.include_name:
|
||||
payload["logger"] = record.name
|
||||
if self.include_pid:
|
||||
payload["process"] = record.process
|
||||
if self.include_thread:
|
||||
payload["thread"] = record.threadName
|
||||
if record.exc_info:
|
||||
payload["exc"] = self.formatException(record.exc_info)
|
||||
|
||||
return json.dumps(payload, ensure_ascii=False)
|
||||
93
uv.lock
generated
Normal file
93
uv.lock
generated
Normal file
@ -0,0 +1,93 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.11"
|
||||
|
||||
[[package]]
|
||||
name = "jalali-core"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2b/3c/21e32e3444c572174a5d774643eb2aa8ab60ef68b99a4c3585a0a11428b4/jalali_core-1.0.0.tar.gz", hash = "sha256:f4287c70c630323dcf0a3ab26df905ba4d451e230ac1f65b3bb2f77797894a2b", size = 2752, upload-time = "2024-03-25T09:36:14.934Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/20/a4e942f9685df720a106da292e29a53212b27903749cc563b86b612b113e/jalali_core-1.0.0-py3-none-any.whl", hash = "sha256:84e6f5090eadfb35234f24fad084be831d00da3c0b238ee001e8a1fd49bf7924", size = 3616, upload-time = "2024-03-25T09:36:13.133Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jdatetime"
|
||||
version = "5.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jalali-core" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6e/9d/5ed59c36f3cbc68c01fab6442e6efb6d35a484ba4eec4f790264fce39f6c/jdatetime-5.2.0.tar.gz", hash = "sha256:c81d5898717b82b609a3ce2a73f8b8d3230b0c757e5c0de9d6b1acfdc224f551", size = 21663, upload-time = "2025-01-26T09:29:16.802Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/39/0dd2676d08468692606645db5ea40091290dc20747ff59636c21c0567d3c/jdatetime-5.2.0-py3-none-any.whl", hash = "sha256:d4aa73543e4e6c0e6122b58743773168edee5efe5c5acf05d1dc8c90524ca71c", size = 12199, upload-time = "2025-01-26T09:29:15.038Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "4.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mdurl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "py-reza-logging"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "jdatetime" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
jalali = [
|
||||
{ name = "jdatetime" },
|
||||
]
|
||||
rich = [
|
||||
{ name = "rich" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "jdatetime", specifier = ">=5.2.0" },
|
||||
{ name = "jdatetime", marker = "extra == 'jalali'", specifier = ">=5.2.0" },
|
||||
{ name = "rich", specifier = ">=14.3.2" },
|
||||
{ name = "rich", marker = "extra == 'rich'", specifier = ">=14.3.2" },
|
||||
]
|
||||
provides-extras = ["rich", "jalali"]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||
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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "14.3.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" },
|
||||
]
|
||||
Loading…
x
Reference in New Issue
Block a user