Compare commits

...
Sign in to create a new pull request.

28 commits

Author SHA1 Message Date
47bba589f4 chore(deps): update dependency faker to v40.11.0
All checks were successful
Continuous Integration / Lint, Check & Test (push) Successful in 42s
Continuous Integration / Build Package (push) Successful in 35s
2026-03-14 00:04:44 +00:00
ac0a6683de chore(deps): update dependency ruff to v0.15.6
All checks were successful
Continuous Integration / Build Package (push) Successful in 23s
Continuous Integration / Lint, Check & Test (push) Successful in 32s
2026-03-13 00:06:24 +00:00
1fb1ea1615 chore(deps): update dependency ruff to v0.15.5
All checks were successful
Continuous Integration / Build Package (push) Successful in 27s
Continuous Integration / Lint, Check & Test (push) Successful in 35s
2026-03-06 00:06:05 +00:00
42177b94bd chore(deps): update dependency faker to v40.8.0
All checks were successful
Continuous Integration / Build Package (push) Successful in 22s
Continuous Integration / Lint, Check & Test (push) Successful in 26s
2026-03-05 00:05:33 +00:00
5132814a77 chore(deps): update dependency basedpyright to v1.38.2
All checks were successful
Continuous Integration / Build Package (push) Successful in 23s
Continuous Integration / Lint, Check & Test (push) Successful in 38s
2026-02-27 01:07:14 +01:00
bb02dd715b chore(deps): update dependency ruff to v0.15.4
Some checks failed
Continuous Integration / Build Package (push) Has been cancelled
Continuous Integration / Lint, Check & Test (push) Has been cancelled
2026-02-27 00:05:47 +00:00
2c26f4ce56 chore(deps): update dependency faker to v40.5.1
All checks were successful
Continuous Integration / Build Package (push) Successful in 36s
Continuous Integration / Lint, Check & Test (push) Successful in 41s
2026-02-24 00:03:44 +00:00
51ee07dbbe fix(deps): update dependency typer to v0.24.1
All checks were successful
Continuous Integration / Build Package (push) Successful in 22s
Continuous Integration / Lint, Check & Test (push) Successful in 27s
2026-02-22 00:05:21 +00:00
0ed680f0b1 fix(deps): update dependency rich to v14.3.3
All checks were successful
Continuous Integration / Build Package (push) Successful in 42s
Continuous Integration / Lint, Check & Test (push) Successful in 51s
2026-02-20 15:53:52 +00:00
49fb18d342 chore(deps): update dependency ruff to v0.15.2
All checks were successful
Continuous Integration / Build Package (push) Successful in 30s
Continuous Integration / Lint, Check & Test (push) Successful in 46s
2026-02-20 01:09:00 +01:00
ea42b31a1e fix(deps): update dependency pydantic-settings to v2.13.1
Some checks failed
Continuous Integration / Build Package (push) Has been cancelled
Continuous Integration / Lint, Check & Test (push) Has been cancelled
2026-02-20 00:07:01 +00:00
bfb9b78ef5 chore(deps): update dependency basedpyright to v1.38.1
All checks were successful
Continuous Integration / Build Package (push) Successful in 33s
Continuous Integration / Lint, Check & Test (push) Successful in 38s
2026-02-19 00:05:21 +00:00
ad751696cb fix(deps): update dependency typer to v0.24.0
All checks were successful
Continuous Integration / Build Package (push) Successful in 31s
Continuous Integration / Lint, Check & Test (push) Successful in 47s
2026-02-18 01:09:17 +01:00
47a9727198 chore(deps): update python docker tag to v3.14
Some checks failed
Continuous Integration / Lint, Check & Test (push) Has been cancelled
Continuous Integration / Build Package (push) Has been cancelled
2026-02-18 01:08:28 +01:00
1dc41ecb3e fix(deps): update dependency pydantic-settings to v2.13.0
Some checks failed
Continuous Integration / Lint, Check & Test (push) Has been cancelled
Continuous Integration / Build Package (push) Has been cancelled
2026-02-18 00:07:10 +00:00
20a3e8b437
feat: add streamd logo
All checks were successful
Continuous Integration / Build Package (push) Successful in 31s
Continuous Integration / Lint, Check & Test (push) Successful in 47s
2026-02-15 17:40:17 +01:00
d89ad8b131
build: add /result to .gitignore
All checks were successful
Continuous Integration / Build Package (push) Successful in 1m11s
Continuous Integration / Lint, Check & Test (push) Successful in 1m24s
2026-02-15 17:33:27 +01:00
ca6b5bbd4d
fix: include dev deps in basedpyright check environment
Use workspace.deps.all instead of workspace.deps.default so that
basedpyright can resolve test dependencies like faker.
2026-02-15 17:33:26 +01:00
ce5e476b23
ci: split workflow into check and build jobs 2026-02-15 17:33:25 +01:00
b4848bb661
build: migrate to devenv
- Add devenv.nix with Python/uv setup and git-hooks
- Add devenv.yaml and devenv.lock
- Refactor flake.nix to use devenv integration
- Update .envrc from flake to devenv
- Add devenv artifacts to .gitignore
2026-02-15 17:33:24 +01:00
f9ed0463f7
refactor: rename test/ to tests/ 2026-02-15 17:33:23 +01:00
af2debc19b
refactor!: rename package from streamer to streamd
- Rename src/streamer/ to src/streamd/
- Update all internal imports
- Update pyproject.toml project name and entry point
- Update README branding (Streamer -> Strea.md)
- Switch from pyright to basedpyright
- Bump requires-python to >=3.13
2026-02-15 17:33:22 +01:00
49cd9bcfa0
fix: resolve all basedpyright warnings
- Use collections.abc.Generator/Iterable instead of deprecated typing imports
- Replace Optional with union syntax (X | None)
- Add explicit type annotations to eliminate reportUnknownVariableType
- Use typing.cast for untyped mistletoe attributes (content, level, line_number)
- Replace mutable default arguments with None defaults (reportCallInDefaultInitializer)
- Add ClassVar annotation for model_config (reportIncompatibleVariableOverride)
- Add @override decorator for settings_customise_sources (reportImplicitOverride)
- Annotate class attributes in Tag (reportUnannotatedClassAttribute)
- Add parameter type annotations in test (reportMissingParameterType)
- Assign unused call result to _ (reportUnusedCallResult)
2026-02-15 17:33:21 +01:00
1e203d9db3 fix(deps): update dependency typer to v0.23.1
All checks were successful
Continuous Integration / Lint, Check & Test (push) Successful in 44s
2026-02-15 01:07:42 +01:00
2298bdaa8f chore(deps): update dependency ruff to v0.15.1
Some checks failed
Continuous Integration / Lint, Check & Test (push) Has been cancelled
2026-02-15 00:06:41 +00:00
646241f355
feat: add home-manager module output
All checks were successful
Continuous Integration / Lint, Check & Test (push) Successful in 1m19s
2026-02-14 18:00:42 +01:00
c0911307fd
docs: expand README with project overview, concepts, and usage 2026-02-14 18:00:41 +01:00
34ba9869d1 fix(deps): update dependency typer to v0.23.0
All checks were successful
Continuous Integration / Lint, Check & Test (push) Successful in 42s
2026-02-12 00:06:12 +00:00
39 changed files with 952 additions and 389 deletions

4
.envrc
View file

@ -1 +1,3 @@
use flake .#impure #!/usr/bin/env bash
eval "$(devenv direnvrc)"
use devenv

View file

@ -5,7 +5,7 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build-and-lint: check:
name: Lint, Check & Test name: Lint, Check & Test
runs-on: nix runs-on: nix
@ -16,8 +16,12 @@ jobs:
- run: nix --version - run: nix --version
- run: nix flake check - run: nix flake check
- name: Install the project build:
run: 'nix develop .#impure --command bash -c "uv sync --locked --all-extras --dev"' name: Build Package
runs-on: nix
- name: Test with PyTest steps:
run: nix develop .#impure --command bash -c "uv run pytest --junit-xml test-report.xml" - name: Check out Repository
uses: https://git.konstantinfickel.de/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- run: nix build

5
.gitignore vendored
View file

@ -177,3 +177,8 @@ pyrightconfig.json
.direnv .direnv
test-report.xml test-report.xml
.devenv
.devenv.flake.nix
.pre-commit-config.yaml
result

View file

@ -1 +1 @@
3.14.3 3.14

View file

@ -1,7 +1,36 @@
# streamer # strea.md
Searching for `@tags` in time-based [streams](https://www.cs.yale.edu/homes/freeman/lifestreams.html). ![The Strea.md-Logo: A tag on an endless paper roll](./streamd.svg)
# Usage Strea.md is a personal knowledge management and time-tracking CLI tool. It organizes time-ordered markdown files using `@tag` annotations, letting you manage tasks, track time, and query your notes from the terminal.
Running `streamer` finds all lines with @Task ## Core Concepts
- **Shards** — Sections of markdown files, organized hierarchically by headings. Each shard can contain markers, tags, and nested child shards.
- **Markers** — Special `@tags` like `@Task`, `@Done`, `@Waiting`, or `@Timesheet` that give shards semantic meaning and place them into dimensions.
- **Dimensions** — Classification axes (e.g. task state, project, timesheet) that categorize shards. Some dimensions propagate to child shards.
## File Format
Markdown files are named with a timestamp: `YYYYMMDD-HHMMSS [markers].md`
For example: `20260131-210000 Task Streamd.md`
Within files, `@`-prefixed markers at the beginning of paragraphs or headings define how a shard is categorized.
## Commands
- `streamd` / `streamd new` — Create a new timestamped markdown entry, opening your editor
- `streamd todo` — Show all open tasks (shards with `@Task` markers)
- `streamd edit [number]` — Edit a stream file by index (most recent first)
- `streamd timesheet` — Generate time reports from `@Timesheet` markers
## Configuration
Streamd reads its configuration from `~/.config/streamd/config.yaml` (XDG standard). The main setting is `base_folder`, which points to the directory containing your stream files (defaults to the current working directory).
## Usage
Running `streamd` opens your editor to create a new entry. After saving, the file is renamed based on its timestamp and any markers found in the content.
Running `streamd todo` finds all shards marked as open tasks and displays them as rich-formatted panels in your terminal.

123
devenv.lock Normal file
View file

@ -0,0 +1,123 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1771157881,
"owner": "cachix",
"repo": "devenv",
"rev": "b0b3dfa70ec90fa49f672e579f186faf4f61bd4b",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"owner": "NixOS",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1770726378,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "5eaaedde414f6eb1aea8b8525c466dc37bba95ae",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1762808025,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"inputs": {
"nixpkgs-src": "nixpkgs-src"
},
"locked": {
"lastModified": 1770434727,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "8430f16a39c27bdeef236f1eeb56f0b51b33d348",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs-src": {
"flake": false,
"locked": {
"lastModified": 1769922788,
"narHash": "sha256-H3AfG4ObMDTkTJYkd8cz1/RbY9LatN5Mk4UF48VuSXc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "207d15f1a6603226e1e223dc79ac29c7846da32e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": [
"git-hooks"
]
}
}
},
"root": "root",
"version": 7
}

28
devenv.nix Normal file
View file

@ -0,0 +1,28 @@
{
pkgs,
...
}:
{
languages = {
python = {
enable = true;
uv.enable = true;
};
};
packages = [
pkgs.basedpyright
];
git-hooks.hooks = {
basedpyright = {
enable = true;
entry = "${pkgs.basedpyright}/bin/basedpyright";
files = "\\.py$";
types = [ "file" ];
};
ruff.enable = true;
ruff-format.enable = true;
commitizen.enable = true;
};
}

6
devenv.yaml Normal file
View file

@ -0,0 +1,6 @@
inputs:
git-hooks:
url: github:cachix/git-hooks.nix
inputs:
nixpkgs:
follows: nixpkgs

82
flake.lock generated
View file

@ -1,16 +1,75 @@
{ {
"nodes": { "nodes": {
"nixpkgs": { "flake-compat": {
"flake": false,
"locked": { "locked": {
"lastModified": 1769789167, "lastModified": 1767039857,
"narHash": "sha256-kKB3bqYJU5nzYeIROI82Ef9VtTbu4uA3YydSk/Bioa8=", "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "flake-compat",
"rev": "62c8382960464ceb98ea593cb8321a2cf8f9e3e5", "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1770726378,
"narHash": "sha256-kck+vIbGOaM/dHea7aTBxdFYpeUl/jHOy5W3eyRvVx8=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "5eaaedde414f6eb1aea8b8525c466dc37bba95ae",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1771008912,
"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a82ccc39b39b621151d6732718e3e250109076fa",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable", "ref": "nixos-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
@ -29,11 +88,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1763662255, "lastModified": 1771039651,
"narHash": "sha256-4bocaOyLa3AfiS8KrWjZQYu+IAta05u3gYZzZ6zXbT0=", "narHash": "sha256-WZOfX4APbc6vmL14ZWJXgBeRfEER8H+OIX0D0nSmv0M=",
"owner": "pyproject-nix", "owner": "pyproject-nix",
"repo": "build-system-pkgs", "repo": "build-system-pkgs",
"rev": "042904167604c681a090c07eb6967b4dd4dae88c", "rev": "69bc2b53b79cbd6ce9f66f506fc962b45b5e68b9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -64,6 +123,7 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"pyproject-build-systems": "pyproject-build-systems", "pyproject-build-systems": "pyproject-build-systems",
"pyproject-nix": "pyproject-nix", "pyproject-nix": "pyproject-nix",
@ -80,11 +140,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1769957392, "lastModified": 1770770348,
"narHash": "sha256-6PkqwwYf5K2CHi2V+faI/9pqjfz/HxUkI/MVid6hlOY=", "narHash": "sha256-A2GzkmzdYvdgmMEu5yxW+xhossP+txrYb7RuzRaqhlg=",
"owner": "pyproject-nix", "owner": "pyproject-nix",
"repo": "uv2nix", "repo": "uv2nix",
"rev": "d18bc50ae1c3d4be9c41c2d94ea765524400af75", "rev": "5d1b2cb4fe3158043fbafbbe2e46238abbc954b0",
"type": "github" "type": "github"
}, },
"original": { "original": {

317
flake.nix
View file

@ -1,8 +1,8 @@
{ {
description = "Hello world flake using uv2nix"; description = "Using Markdown Files to organize your life as a @Tag-Stream";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
pyproject-nix = { pyproject-nix = {
url = "github:pyproject-nix/pyproject.nix"; url = "github:pyproject-nix/pyproject.nix";
@ -21,204 +21,215 @@
inputs.uv2nix.follows = "uv2nix"; inputs.uv2nix.follows = "uv2nix";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
git-hooks = {
url = "github:cachix/git-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = outputs =
{ {
self, self,
nixpkgs, nixpkgs,
uv2nix,
pyproject-nix, pyproject-nix,
uv2nix,
pyproject-build-systems, pyproject-build-systems,
git-hooks,
... ...
}: }:
let let
inherit (nixpkgs) lib; inherit (nixpkgs) lib;
forAllSystems = lib.genAttrs lib.systems.flakeExposed;
# Load a uv workspace from a workspace root.
# Uv2nix treats all uv projects as workspace projects.
workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; }; workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
# Create package overlay from workspace.
overlay = workspace.mkPyprojectOverlay { overlay = workspace.mkPyprojectOverlay {
# Prefer prebuilt binary wheels as a package source. sourcePreference = "wheel";
# Sdists are less likely to "just work" because of the metadata missing from uv.lock.
# Binary wheels are more likely to, but may still require overrides for library dependencies.
sourcePreference = "wheel"; # or sourcePreference = "sdist";
# Optionally customise PEP 508 environment
# environ = {
# platform_release = "5.10.65";
# };
}; };
# Extend generated overlay with build fixups editableOverlay = workspace.mkEditablePyprojectOverlay {
# root = "$REPO_ROOT";
# Uv2nix can only work with what it has, and uv.lock is missing essential metadata to perform some builds.
# This is an additional overlay implementing build fixups.
# See:
# - https://pyproject-nix.github.io/uv2nix/FAQ.html
pyprojectOverrides = _final: _prev: {
# Implement build fixups here.
# Note that uv2nix is _not_ using Nixpkgs buildPythonPackage.
# It's using https://pyproject-nix.github.io/pyproject.nix/build.html
}; };
# This example is only using x86_64-linux pythonSets = forAllSystems (
pkgs = nixpkgs.legacyPackages.x86_64-linux; system:
let
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs) stdenv;
# Use Python 3.14 from nixpkgs baseSet = pkgs.callPackage pyproject-nix.build.packages {
python = pkgs.python314; python = pkgs.python313;
};
# Construct package set pyprojectOverrides = _final: prev: {
pythonSet = streamd = prev.streamd.overrideAttrs (old: {
# Use base package set from pyproject.nix builders passthru = old.passthru // {
(pkgs.callPackage pyproject-nix.build.packages { tests = (old.passthru.tests or { }) // {
inherit python; pytest = stdenv.mkDerivation {
}).overrideScope name = "${_final.streamd.name}-pytest";
( inherit (_final.streamd) src;
nativeBuildInputs = [
(_final.mkVirtualEnv "streamd-pytest-env" {
streamd = [ "dev" ];
})
];
dontConfigure = true;
buildPhase = ''
runHook preBuild
# Exit code 5 means no tests collected — allow it so the
# check succeeds on an empty test suite.
pytest || [ $? -eq 5 ]
runHook postBuild
'';
installPhase = ''
runHook preInstall
touch $out
runHook postInstall
'';
};
};
};
});
};
in
baseSet.overrideScope (
lib.composeManyExtensions [ lib.composeManyExtensions [
pyproject-build-systems.overlays.default pyproject-build-systems.overlays.default
overlay overlay
pyprojectOverrides pyprojectOverrides
] ]
)
); );
in mkGitHooksCheck =
{ system:
# Package a virtual environment as our main application.
#
# Enable no optional dependencies for production build.
packages.x86_64-linux =
let let
streamer = pythonSet.mkVirtualEnv "streamer-env" workspace.deps.default; pkgs = nixpkgs.legacyPackages.${system};
pythonSet = pythonSets.${system};
venv = pythonSet.mkVirtualEnv "streamd-check-env" workspace.deps.all;
in in
{ git-hooks.lib.${system}.run {
inherit streamer; src = ./.;
default = streamer; hooks = {
}; basedpyright = {
enable = true;
# Make streamer runnable with `nix run` entry = "${pkgs.basedpyright}/bin/basedpyright --pythonpath ${venv}/bin/python --project ${
apps.x86_64-linux = { pkgs.writeText "pyrightconfig.json" (
default = { builtins.toJSON {
type = "app"; reportMissingTypeStubs = false;
program = "${self.packages.x86_64-linux.default}/bin/streamer"; reportUnnecessaryTypeIgnoreComment = false;
};
};
# This example provides two different modes of development:
# - Impurely using uv to manage virtual environments
# - Pure development using uv2nix to manage virtual environments
devShells.x86_64-linux = {
# It is of course perfectly OK to keep using an impure virtualenv workflow and only use uv2nix to build packages.
# This devShell simply adds Python and undoes the dependency leakage done by Nixpkgs Python infrastructure.
impure = pkgs.mkShell {
packages = with pkgs; [
python
uv
pre-commit
bashInteractive
];
env = {
# Prevent uv from managing Python downloads
UV_PYTHON_DOWNLOADS = "never";
# Force uv to use nixpkgs Python interpreter
UV_PYTHON = python.interpreter;
} }
// lib.optionalAttrs pkgs.stdenv.isLinux { )
# Python libraries often load native shared objects using dlopen(3). }";
# Setting LD_LIBRARY_PATH makes the dynamic library loader aware of libraries without using RPATH for lookup. files = "\\.py$";
LD_LIBRARY_PATH = lib.makeLibraryPath pkgs.pythonManylinuxPackages.manylinux1; types = [ "file" ];
}; };
shellHook = '' ruff.enable = true;
unset PYTHONPATH ruff-format.enable = true;
''; commitizen.enable = true;
}; };
# This devShell uses uv2nix to construct a virtual environment purely from Nix, using the same dependency specification as the application.
# The notable difference is that we also apply another overlay here enabling editable mode ( https://setuptools.pypa.io/en/latest/userguide/development_mode.html ).
#
# This means that any changes done to your local files do not require a rebuild.
#
# Note: Editable package support is still unstable and subject to change.
uv2nix =
let
# Create an overlay enabling editable mode for all local dependencies.
editableOverlay = workspace.mkEditablePyprojectOverlay {
# Use environment variable
root = "$REPO_ROOT";
# Optional: Only enable editable for these packages
# members = [ "streamer" ];
}; };
# Override previous set with our overrideable overlay.
editablePythonSet = pythonSet.overrideScope (
lib.composeManyExtensions [
editableOverlay
# Apply fixups for building an editable package of your workspace packages
(final: prev: {
streamer = prev.streamer.overrideAttrs (old: {
# It's a good idea to filter the sources going into an editable build
# so the editable package doesn't have to be rebuilt on every change.
src = lib.fileset.toSource {
root = old.src;
fileset = lib.fileset.unions [
(old.src + "/pyproject.toml")
(old.src + "/README.md")
(old.src + "/src/streamer/__init__.py")
];
};
# Hatchling (our build system) has a dependency on the `editables` package when building editables.
#
# In normal Python flows this dependency is dynamically handled, and doesn't need to be explicitly declared.
# This behaviour is documented in PEP-660.
#
# With Nix the dependency needs to be explicitly declared.
nativeBuildInputs =
old.nativeBuildInputs
++ final.resolveBuildSystem {
editables = [ ];
};
});
})
]
);
# Build virtual environment, with local packages being editable.
#
# Enable all optional dependencies for development.
virtualenv = editablePythonSet.mkVirtualEnv "streamer-dev-env" workspace.deps.all;
in in
pkgs.mkShell { {
packages = with pkgs; [ packages = forAllSystems (
system:
let
pythonSet = pythonSets.${system};
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs.callPackages pyproject-nix.build.util { }) mkApplication;
in
rec {
streamd = mkApplication {
venv = pythonSet.mkVirtualEnv "streamd-env" workspace.deps.default;
package = pythonSet.streamd;
};
default = streamd;
}
);
homeManagerModules.default =
{
lib,
config,
pkgs,
...
}:
let
cfg = config.programs.streamd;
in
{
options.programs.streamd = {
enable = lib.mkEnableOption "streamd";
base-folder = lib.mkOption {
type = lib.types.str;
description = "Base Folder of streamd";
};
package = lib.mkOption {
type = lib.types.package;
default = self.packages.${pkgs.system}.streamd;
defaultText = lib.literalExpression "inputs.streamd.packages.\${pkgs.system}.streamd";
description = "The package to use for the streamd binary.";
};
};
config = lib.mkIf cfg.enable {
assertions = [
(lib.hm.assertions.assertPlatform "programs.streamd" pkgs lib.platforms.linux)
];
home.packages = [ cfg.package ];
xdg.configFile."streamd/config.yaml".source =
(pkgs.formats.yaml { }).generate "streamd-configuration"
{
base_folder = cfg.base-folder;
};
home.shellAliases.s = "streamd";
};
};
checks = forAllSystems (
system:
let
pythonSet = pythonSets.${system};
in
{
inherit (pythonSet.streamd.passthru.tests) pytest;
pre-commit = mkGitHooksCheck system;
}
);
devShells = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
pythonSet = pythonSets.${system}.overrideScope editableOverlay;
virtualenv = pythonSet.mkVirtualEnv "streamd-dev-env" workspace.deps.all;
in
{
default = pkgs.mkShell {
packages = [
virtualenv virtualenv
uv pkgs.uv
pre-commit
bashInteractive
]; ];
env = { env = {
# Don't create venv using uv
UV_NO_SYNC = "1"; UV_NO_SYNC = "1";
UV_PYTHON = pythonSet.python.interpreter;
# Force uv to use Python interpreter from venv
UV_PYTHON = "${virtualenv}/bin/python";
# Prevent uv from downloading managed Python's
UV_PYTHON_DOWNLOADS = "never"; UV_PYTHON_DOWNLOADS = "never";
}; };
shellHook = '' shellHook = ''
# Undo dependency propagation by nixpkgs.
unset PYTHONPATH unset PYTHONPATH
# Get repository root using git. This is expanded at runtime by the editable `.pth` machinery.
export REPO_ROOT=$(git rev-parse --show-toplevel) export REPO_ROOT=$(git rev-parse --show-toplevel)
${(mkGitHooksCheck system).shellHook}
''; '';
}; };
}; }
);
}; };
} }

View file

@ -1,21 +1,21 @@
[project] [project]
name = "streamer" name = "streamd"
version = "0.1.0" version = "0.1.0"
description = "Searching for tags in streams" description = "Searching for tags in streams"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.13"
dependencies = [ dependencies = [
"click==8.3.1", "click==8.3.1",
"mistletoe==1.5.1", "mistletoe==1.5.1",
"pydantic==2.12.5", "pydantic==2.12.5",
"pydantic-settings[yaml]==2.12.0", "pydantic-settings[yaml]==2.13.1",
"rich==14.3.2", "rich==14.3.3",
"typer==0.21.2", "typer==0.24.1",
"xdg-base-dirs==6.0.2", "xdg-base-dirs==6.0.2",
] ]
[project.scripts] [project.scripts]
streamer = "streamer:app" streamd = "streamd:app"
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
@ -23,8 +23,8 @@ build-backend = "hatchling.build"
[dependency-groups] [dependency-groups]
dev = [ dev = [
"faker==40.4.0", "basedpyright==1.38.2",
"pyright==1.1.408", "faker==40.11.0",
"pytest==9.0.2", "pytest==9.0.2",
"ruff==0.15.0", "ruff==0.15.6",
] ]

View file

@ -1,8 +1,9 @@
import glob import glob
import os import os
from collections.abc import Generator
from datetime import datetime from datetime import datetime
from shutil import move from shutil import move
from typing import Annotated, Generator from typing import Annotated
import click import click
import typer import typer
@ -10,18 +11,17 @@ from rich import print
from rich.markdown import Markdown from rich.markdown import Markdown
from rich.panel import Panel from rich.panel import Panel
from streamer.localize import ( from streamd.localize import (
LocalizedShard, LocalizedShard,
RepositoryConfiguration, RepositoryConfiguration,
localize_stream_file, localize_stream_file,
) )
from streamer.localize.preconfigured_configurations import TaskConfiguration from streamd.localize.preconfigured_configurations import TaskConfiguration
from streamer.parse import parse_markdown_file from streamd.parse import parse_markdown_file
from streamer.query import find_shard_by_position from streamd.query import find_shard_by_position
from streamer.query.find import find_shard_by_set_dimension from streamd.settings import Settings
from streamer.settings import Settings from streamd.timesheet.configuration import BasicTimesheetConfiguration
from streamer.timesheet.configuration import BasicTimesheetConfiguration from streamd.timesheet.extract import extract_timesheets
from streamer.timesheet.extract import extract_timesheets
app = typer.Typer() app = typer.Typer()
@ -89,11 +89,11 @@ def timesheet() -> None:
@app.command() @app.command()
def new() -> None: def new() -> None:
streamer_directory = Settings().base_folder streamd_directory = Settings().base_folder
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
preliminary_file_name = f"{timestamp}_wip.md" preliminary_file_name = f"{timestamp}_wip.md"
prelimary_path = os.path.join(streamer_directory, preliminary_file_name) prelimary_path = os.path.join(streamd_directory, preliminary_file_name)
content = "# " content = "# "
with open(prelimary_path, "w") as file: with open(prelimary_path, "w") as file:
@ -111,7 +111,7 @@ def new() -> None:
): ):
final_file_name = f"{timestamp} {' '.join(markers)}.md" final_file_name = f"{timestamp} {' '.join(markers)}.md"
final_path = os.path.join(streamer_directory, final_file_name) final_path = os.path.join(streamd_directory, final_file_name)
_ = move(prelimary_path, final_path) _ = move(prelimary_path, final_path)
print(f"Saved as [yellow]{final_file_name}") print(f"Saved as [yellow]{final_file_name}")

View file

@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
from streamer.parse.shard import Shard, StreamFile from streamd.parse.shard import Shard, StreamFile
from .extract_datetime import ( from .extract_datetime import (
extract_datetime_from_file_name, extract_datetime_from_file_name,
@ -47,7 +47,10 @@ def localize_shard(
position.update(private_position) position.update(private_position)
return LocalizedShard( return LocalizedShard(
**shard.model_dump(exclude={"children"}), markers=shard.markers,
tags=shard.tags,
start_line=shard.start_line,
end_line=shard.end_line,
location=position, location=position,
children=children, children=children,
moment=adjusted_moment, moment=adjusted_moment,

View file

@ -2,7 +2,7 @@ from __future__ import annotations
from datetime import datetime from datetime import datetime
from streamer.parse.shard import Shard from streamd.parse.shard import Shard
class LocalizedShard(Shard): class LocalizedShard(Shard):

View file

@ -1,4 +1,4 @@
from streamer.localize.repository_configuration import ( from streamd.localize.repository_configuration import (
Dimension, Dimension,
Marker, Marker,
MarkerPlacement, MarkerPlacement,

View file

@ -1,13 +1,11 @@
from __future__ import annotations from __future__ import annotations
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
class Dimension(BaseModel): class Dimension(BaseModel):
display_name: str display_name: str
comment: Optional[str] = None comment: str | None = None
propagate: bool = False propagate: bool = False
@ -29,7 +27,7 @@ class RepositoryConfiguration(BaseModel):
def merge_single_dimension(base: Dimension, second: Dimension) -> Dimension: def merge_single_dimension(base: Dimension, second: Dimension) -> Dimension:
second_fields_set = getattr(second, "model_fields_set", set()) second_fields_set: set[str] = getattr(second, "model_fields_set", set())
return Dimension( return Dimension(
display_name=second.display_name or base.display_name, display_name=second.display_name or base.display_name,

View file

@ -1,7 +1,9 @@
import re import re
from typing import Iterable from collections.abc import Iterable
from typing import cast
from mistletoe.block_token import BlockToken from mistletoe.block_token import BlockToken
from mistletoe.span_token import Emphasis, RawText, Strikethrough, Strong, Link from mistletoe.span_token import Emphasis, Link, RawText, Strikethrough, Strong
from mistletoe.token import Token from mistletoe.token import Token
from .markdown_tag import Tag from .markdown_tag import Tag
@ -12,18 +14,21 @@ def extract_markers_and_tags_from_single_token(
marker_boundary_encountered: bool, marker_boundary_encountered: bool,
return_at_first_marker: bool = False, return_at_first_marker: bool = False,
) -> tuple[list[str], list[str], bool]: ) -> tuple[list[str], list[str], bool]:
result_markers, result_tags = [], [] result_markers: list[str] = []
result_tags: list[str] = []
result_marker_boundary_encountered = marker_boundary_encountered result_marker_boundary_encountered = marker_boundary_encountered
if isinstance(token, Tag): if isinstance(token, Tag):
content = cast(str, token.content)
if marker_boundary_encountered: if marker_boundary_encountered:
result_tags.append(token.content) result_tags.append(content)
else: else:
result_markers.append(token.content) result_markers.append(content)
elif isinstance(token, (Emphasis, Strong, Strikethrough, Link)): elif isinstance(token, (Emphasis, Strong, Strikethrough, Link)):
children = list(token.children or [])
markers, tags, child_marker_boundary_encountered = ( markers, tags, child_marker_boundary_encountered = (
extract_markers_and_tags_from_tokens( extract_markers_and_tags_from_tokens(
token.children or [], children,
marker_boundary_encountered, marker_boundary_encountered,
return_at_first_marker, return_at_first_marker,
) )
@ -33,8 +38,10 @@ def extract_markers_and_tags_from_single_token(
result_marker_boundary_encountered = ( result_marker_boundary_encountered = (
marker_boundary_encountered or child_marker_boundary_encountered marker_boundary_encountered or child_marker_boundary_encountered
) )
elif isinstance(token, RawText) and re.match(r"^[\s]*$", token.content): elif isinstance(token, RawText):
pass content_raw = cast(str, token.content)
if not re.match(r"^[\s]*$", content_raw):
result_marker_boundary_encountered = True
else: else:
result_marker_boundary_encountered = True result_marker_boundary_encountered = True
@ -46,7 +53,8 @@ def extract_markers_and_tags_from_tokens(
marker_boundary_encountered: bool, marker_boundary_encountered: bool,
return_at_first_marker: bool = False, return_at_first_marker: bool = False,
) -> tuple[list[str], list[str], bool]: ) -> tuple[list[str], list[str], bool]:
result_markers, result_tags = [], [] result_markers: list[str] = []
result_tags: list[str] = []
result_marker_boundary_encountered = marker_boundary_encountered result_marker_boundary_encountered = marker_boundary_encountered
for child in tokens: for child in tokens:
@ -68,15 +76,15 @@ def extract_markers_and_tags_from_tokens(
def extract_markers_and_tags(block_token: BlockToken) -> tuple[list[str], list[str]]: def extract_markers_and_tags(block_token: BlockToken) -> tuple[list[str], list[str]]:
markers, tags, _ = extract_markers_and_tags_from_tokens( children = list(block_token.children or [])
block_token.children or [], False markers, tags, _ = extract_markers_and_tags_from_tokens(children, False)
)
return markers, tags return markers, tags
def has_markers(block_token: BlockToken) -> bool: def has_markers(block_token: BlockToken) -> bool:
children = list(block_token.children or [])
markers, _, _ = extract_markers_and_tags_from_tokens( markers, _, _ = extract_markers_and_tags_from_tokens(
block_token.children or [], False, return_at_first_marker=True children, False, return_at_first_marker=True
) )
return len(markers) > 0 return len(markers) > 0

View file

@ -0,0 +1,23 @@
import re
from typing import cast
from mistletoe.markdown_renderer import Fragment, MarkdownRenderer
from mistletoe.span_token import SpanToken
class Tag(SpanToken):
parse_inner: bool = False
pattern: re.Pattern[str] = re.compile(r"@([^\s*\x60~\[\]]+)")
class TagMarkdownRenderer(MarkdownRenderer):
def __init__(self) -> None:
super().__init__(Tag) # pyright: ignore[reportUnknownMemberType]
def render_tag(self, token: Tag):
content = cast(str, token.content)
yield Fragment("@")
yield Fragment(content)
__all__ = ["Tag", "TagMarkdownRenderer"]

View file

@ -1,4 +1,5 @@
from collections import Counter from collections import Counter
from typing import cast
from mistletoe.block_token import ( from mistletoe.block_token import (
BlockToken, BlockToken,
@ -16,16 +17,20 @@ from .shard import Shard, StreamFile
def get_line_number(block_token: BlockToken) -> int: def get_line_number(block_token: BlockToken) -> int:
return block_token.line_number # type: ignore return cast(int, block_token.line_number) # pyright: ignore[reportAttributeAccessIssue]
def build_shard( def build_shard(
start_line: int, start_line: int,
end_line: int, end_line: int,
markers: list[str] = [], markers: list[str] | None = None,
tags: list[str] = [], tags: list[str] | None = None,
children: list[Shard] = [], children: list[Shard] | None = None,
) -> Shard: ) -> Shard:
markers = markers or []
tags = tags or []
children = children or []
if ( if (
len(children) == 1 len(children) == 1
and len(tags) == 0 and len(tags) == 0
@ -45,14 +50,17 @@ def build_shard(
def merge_into_first_shard( def merge_into_first_shard(
shards: list[Shard], start_line: int, end_line: int, additional_tags: list[str] = [] shards: list[Shard],
): start_line: int,
end_line: int,
additional_tags: list[str] | None = None,
) -> Shard:
return shards[0].model_copy( return shards[0].model_copy(
update={ update={
"start_line": start_line, "start_line": start_line,
"end_line": end_line, "end_line": end_line,
"children": shards[1:], "children": shards[1:],
"tags": shards[0].tags + additional_tags, "tags": shards[0].tags + (additional_tags or []),
} }
) )
@ -65,13 +73,18 @@ def find_paragraph_shard_positions(block_tokens: list[BlockToken]) -> list[int]:
] ]
def _heading_level(heading: Heading) -> int:
return cast(int, heading.level)
def find_headings_by_level( def find_headings_by_level(
block_tokens: list[BlockToken], header_level: int block_tokens: list[BlockToken], header_level: int
) -> list[int]: ) -> list[int]:
return [ return [
index index
for index, block_token in enumerate(block_tokens) for index, block_token in enumerate(block_tokens)
if isinstance(block_token, Heading) and block_token.level == header_level if isinstance(block_token, Heading)
and _heading_level(block_token) == header_level
] ]
@ -83,8 +96,8 @@ def calculate_heading_level_for_next_split(
If only the first token is a heading with a marker, then return None. If only the first token is a heading with a marker, then return None.
Otherwise: Return the heading level with the lowest level (h1 < h2), of which there are two or which has a marker (and doesn't stem from first) Otherwise: Return the heading level with the lowest level (h1 < h2), of which there are two or which has a marker (and doesn't stem from first)
""" """
level_of_headings_without_first_with_marker = [ level_of_headings_without_first_with_marker: list[int] = [
token.level _heading_level(token)
for token in block_tokens[1:] for token in block_tokens[1:]
if isinstance(token, Heading) and has_markers(token) if isinstance(token, Heading) and has_markers(token)
] ]
@ -92,8 +105,8 @@ def calculate_heading_level_for_next_split(
if len(level_of_headings_without_first_with_marker) == 0: if len(level_of_headings_without_first_with_marker) == 0:
return None return None
heading_level_counter = Counter( heading_level_counter: Counter[int] = Counter(
[token.level for token in block_tokens if isinstance(token, Heading)] [_heading_level(token) for token in block_tokens if isinstance(token, Heading)]
) )
return min( return min(
@ -105,10 +118,12 @@ def calculate_heading_level_for_next_split(
def parse_single_block_shards( def parse_single_block_shards(
block_token: BlockToken, start_line: int, end_line: int block_token: BlockToken, start_line: int, end_line: int
) -> tuple[Shard | None, list[str]]: ) -> tuple[Shard | None, list[str]]:
markers, tags, children = [], [], [] markers: list[str] = []
tags: list[str] = []
children: list[Shard] = []
if isinstance(block_token, List): if isinstance(block_token, List):
list_items: list[ListItem] = ( # type: ignore list_items: list[ListItem] = ( # pyright: ignore[reportAssignmentType]
list(block_token.children) if block_token.children is not None else [] list(block_token.children) if block_token.children is not None else []
) )
for index, list_item in enumerate(list_items): for index, list_item in enumerate(list_items):
@ -119,7 +134,7 @@ def parse_single_block_shards(
else end_line else end_line
) )
list_item_shard, list_item_tags = parse_multiple_block_shards( list_item_shard, list_item_tags = parse_multiple_block_shards(
list_item.children, # type: ignore list_item.children, # pyright: ignore[reportArgumentType]
list_item_start_line, list_item_start_line,
list_item_end_line, list_item_end_line,
) )
@ -149,7 +164,8 @@ def parse_multiple_block_shards(
) )
paragraph_positions = find_paragraph_shard_positions(block_tokens) paragraph_positions = find_paragraph_shard_positions(block_tokens)
children, tags = [], [] children: list[Shard] = []
tags: list[str] = []
is_first_block_only_with_marker = False is_first_block_only_with_marker = False
@ -201,7 +217,7 @@ def parse_header_shards(
block_tokens_split_by_heading = split_at(block_tokens, heading_positions) block_tokens_split_by_heading = split_at(block_tokens, heading_positions)
children = [] children: list[Shard] = []
for i, child_blocks in enumerate(block_tokens_split_by_heading): for i, child_blocks in enumerate(block_tokens_split_by_heading):
child_start_line = get_line_number(child_blocks[0]) child_start_line = get_line_number(child_blocks[0])
child_end_line = ( child_end_line = (
@ -229,7 +245,7 @@ def parse_markdown_file(file_name: str, file_content: str) -> StreamFile:
with TagMarkdownRenderer(): with TagMarkdownRenderer():
ast = Document(file_content) ast = Document(file_content)
block_tokens: list[BlockToken] = ast.children # type: ignore block_tokens: list[BlockToken] = ast.children # pyright: ignore[reportAssignmentType]
if len(block_tokens) > 0: if len(block_tokens) > 0:
if parsed_shard := parse_header_shards( if parsed_shard := parse_header_shards(
block_tokens, shard.start_line, shard.end_line block_tokens, shard.start_line, shard.end_line

View file

@ -1,12 +1,12 @@
from typing import Callable from typing import Callable
from streamer.localize import LocalizedShard from streamd.localize import LocalizedShard
def find_shard( def find_shard(
shards: list[LocalizedShard], query_function: Callable[[LocalizedShard], bool] shards: list[LocalizedShard], query_function: Callable[[LocalizedShard], bool]
) -> list[LocalizedShard]: ) -> list[LocalizedShard]:
found_shards = [] found_shards: list[LocalizedShard] = []
for shard in shards: for shard in shards:
if query_function(shard): if query_function(shard):
@ -21,8 +21,9 @@ def find_shard_by_position(
) -> list[LocalizedShard]: ) -> list[LocalizedShard]:
return find_shard( return find_shard(
shards, shards,
lambda shard: dimension in shard.location lambda shard: (
and shard.location[dimension] == value, dimension in shard.location and shard.location[dimension] == value
),
) )

View file

@ -1,4 +1,6 @@
import os import os
from typing import ClassVar, override
from pydantic_settings import ( from pydantic_settings import (
BaseSettings, BaseSettings,
PydanticBaseSettingsSource, PydanticBaseSettingsSource,
@ -7,15 +9,18 @@ from pydantic_settings import (
) )
from xdg_base_dirs import xdg_config_home from xdg_base_dirs import xdg_config_home
SETTINGS_FILE = xdg_config_home() / "streamer" / "config.yaml" SETTINGS_FILE = xdg_config_home() / "streamd" / "config.yaml"
class Settings(BaseSettings): class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file_encoding="utf-8") model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
env_file_encoding="utf-8"
)
base_folder: str = os.getcwd() base_folder: str = os.getcwd()
@classmethod @classmethod
@override
def settings_customise_sources( def settings_customise_sources(
cls, cls,
settings_cls: type[BaseSettings], settings_cls: type[BaseSettings],

View file

@ -1,7 +1,7 @@
from enum import StrEnum from enum import StrEnum
from streamer.localize import RepositoryConfiguration from streamd.localize import RepositoryConfiguration
from streamer.localize.repository_configuration import ( from streamd.localize.repository_configuration import (
Dimension, Dimension,
Marker, Marker,
MarkerPlacement, MarkerPlacement,

View file

@ -2,9 +2,8 @@ from datetime import datetime
from itertools import groupby from itertools import groupby
from pydantic import BaseModel from pydantic import BaseModel
from streamd.localize import LocalizedShard
from streamer.localize import LocalizedShard from streamd.query.find import find_shard_by_set_dimension
from streamer.query.find import find_shard_by_set_dimension
from .configuration import TIMESHEET_DIMENSION_NAME, TimesheetPointType from .configuration import TIMESHEET_DIMENSION_NAME, TimesheetPointType
from .timecard import SpecialDayType, Timecard, Timesheet from .timecard import SpecialDayType, Timecard, Timesheet

View file

@ -1,20 +0,0 @@
import re
from mistletoe.markdown_renderer import Fragment, MarkdownRenderer
from mistletoe.span_token import SpanToken
class Tag(SpanToken):
parse_inner = False
pattern = re.compile(r"@([^\s*\x60~\[\]]+)")
class TagMarkdownRenderer(MarkdownRenderer):
def __init__(self):
super().__init__(Tag)
def render_tag(self, token: Tag):
yield Fragment("@")
yield Fragment(token.content)
__all__ = ["Tag", "TagMarkdownRenderer"]

283
streamd.svg Normal file
View file

@ -0,0 +1,283 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="192"
height="192"
viewBox="0 0 192 192"
version="1.1"
id="svg5"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="streamd.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<title
id="title1">Streamd</title>
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="px"
showgrid="true"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-midpoints="true"
inkscape:snap-smooth-nodes="true"
inkscape:zoom="4"
inkscape:cx="58.375"
inkscape:cy="112.25"
inkscape:window-width="2560"
inkscape:window-height="1416"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer3"
inkscape:snap-grids="true"
inkscape:snap-page="true"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid824"
empspacing="4"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px" />
</sodipodi:namedview>
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient3586">
<stop
style="stop-color:#ffffff;stop-opacity:0.1"
offset="0"
id="stop3582" />
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="1"
id="stop3584" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1598">
<ellipse
style="fill:#bbdefb"
id="ellipse1600"
cx="160"
cy="40"
rx="12"
ry="20" />
</clipPath>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1774"
x="-0.043319996"
y="-0.043320001"
width="1.08664"
height="1.08664">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.5060667"
id="feGaussianBlur1776" />
</filter>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1839"
x="-0.047999999"
y="-0.047999999"
width="1.096"
height="1.096">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="3.04"
id="feGaussianBlur1841" />
</filter>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1886"
x="-0.030315789"
y="-0.1152"
width="1.0606316"
height="1.2304">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.92"
id="feGaussianBlur1888" />
</filter>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1932">
<path
style="fill:#e3f2fd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 32,44 v 116 c 0,8 4,12 12,12 40,0 68,-28 115.63984,-28.34001 C 168.99976,143.59319 172,140 172,132 V 44 Z"
id="path1934"
sodipodi:nodetypes="cccsccc" />
</clipPath>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1956"
x="-0.030857142"
y="-0.108"
width="1.0617143"
height="1.216">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.8"
id="feGaussianBlur1958" />
</filter>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1980">
<ellipse
style="fill:#bbdefb"
id="ellipse1982"
cx="160"
cy="40"
rx="12"
ry="20" />
</clipPath>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3586"
id="linearGradient3588"
x1="0"
y1="0"
x2="192"
y2="192"
gradientUnits="userSpaceOnUse" />
</defs>
<g
inkscape:label="Paper"
inkscape:groupmode="layer"
id="layer1"
style="display:inline"
sodipodi:insensitive="true">
<path
id="path1814"
style="opacity:0.2;fill:#000000;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter1839)"
d="M 32,20 A 12,20 0 0 0 20,40 12,20 0 0 0 32,60 v 100 c 0,8 4,12 12,12 40,0 68.00079,-27.99983 115.64062,-28.33984 C 169.00055,143.59336 172,140 172,132 V 44 h -0.26758 A 12,20 0 0 0 172,40 12,20 0 0 0 160,20 Z m 125.57812,0.455078 a 12,20 0 0 0 -0.38867,0.128906 12,20 0 0 1 0.38867,-0.128906 z m -2.58984,1.410156 a 12,20 0 0 0 -0.4043,0.333985 12,20 0 0 1 0.4043,-0.333985 z m -2.40039,2.423828 a 12,20 0 0 0 -0.21484,0.314454 12,20 0 0 1 0.21484,-0.314454 z m -1.82031,2.990235 a 12,20 0 0 0 -0.19727,0.373047 12,20 0 0 1 0.19727,-0.373047 z m -1.44531,3.685547 a 12,20 0 0 0 -0.20508,0.697265 12,20 0 0 1 0.20508,-0.697265 z m -0.97266,4.349609 a 12,20 0 0 0 -0.0762,0.650391 12,20 0 0 1 0.0762,-0.650391 z" />
<path
style="fill:#e0f7fa;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 32,40 v 120 c 0,8 4,12 12,12 40,0 68,-28 115.63984,-28.34001 C 168.99976,143.59319 172,140 172,132 V 40 Z"
id="path2678"
sodipodi:nodetypes="cccsccc" />
<path
id="path2431"
style="opacity:0.2;fill:#000000;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 172,131 c 0,8 -2.99945,11.59336 -12.35938,11.66016 C 112.00079,143.00017 84,171 44,171 36,171 32,167 32,159 v 1 c 0,8 4,12 12,12 40,0 68.00079,-27.99983 115.64062,-28.33984 C 169.00055,143.59336 172,140 172,132 Z" />
<path
id="path1863"
style="opacity:0.2;fill:#000000;stroke-width:0.470751;filter:url(#filter1886)"
d="M 32,20 A 12,20 0 0 0 20,40 12,20 0 0 0 32,60 H 160 A 12,20 0 0 0 172,40 12,20 0 0 0 160,20 Z m 125.57812,0.455078 a 12,20 0 0 0 -0.38867,0.128906 12,20 0 0 1 0.38867,-0.128906 z m -2.58984,1.410156 a 12,20 0 0 0 -0.4043,0.333985 12,20 0 0 1 0.4043,-0.333985 z m -2.40039,2.423828 a 12,20 0 0 0 -0.21484,0.314454 12,20 0 0 1 0.21484,-0.314454 z m -1.82031,2.990235 a 12,20 0 0 0 -0.19727,0.373047 12,20 0 0 1 0.19727,-0.373047 z m -1.44531,3.685547 a 12,20 0 0 0 -0.20508,0.697265 12,20 0 0 1 0.20508,-0.697265 z m -0.97266,4.349609 a 12,20 0 0 0 -0.0762,0.650391 12,20 0 0 1 0.0762,-0.650391 z m -0.0762,8.720703 a 12,20 0 0 0 0.0762,0.650391 12,20 0 0 1 -0.0762,-0.650391 z m 0.84375,4.302735 a 12,20 0 0 0 0.20508,0.697265 12,20 0 0 1 -0.20508,-0.697265 z m 1.45312,4.009765 a 12,20 0 0 0 0.19727,0.373047 12,20 0 0 1 -0.19727,-0.373047 z m 1.80274,3.048828 a 12,20 0 0 0 0.21484,0.314454 12,20 0 0 1 -0.21484,-0.314454 z m 2.21093,2.404297 a 12,20 0 0 0 0.4043,0.333985 12,20 0 0 1 -0.4043,-0.333985 z m 2.60547,1.615235 a 12,20 0 0 0 0.38867,0.128906 12,20 0 0 1 -0.38867,-0.128906 z"
clip-path="url(#clipPath1932)" />
<ellipse
style="fill:#f2f2f2"
id="ellipse2158"
cx="160"
cy="40"
rx="12"
ry="20" />
<path
id="rect1442"
style="fill:#4dd0e1;stroke-width:0.470751"
d="M 32,20 A 12,20 0 0 0 20,40 12,20 0 0 0 32,60 H 160 A 12,20 0 0 1 148,40 12,20 0 0 1 160,20 Z" />
<path
id="path1936"
style="opacity:0.2;fill:#000000;stroke-width:0.470751;filter:url(#filter1956)"
d="M 32,20 A 12,20 0 0 0 20,40 12,20 0 0 0 32,60 H 160 A 12,20 0 0 1 148,40 12,20 0 0 1 160,20 Z"
clip-path="url(#clipPath1980)" />
<path
id="rect1406"
style="opacity:1;fill:#80deea"
d="M 32,20 A 12,20 0 0 0 20,40 h 128 a 12,20 0 0 1 12,-20 z" />
<path
id="path2390"
style="opacity:0.2;fill:#ffffff"
d="M 32,20 A 12,20 0 0 0 20,40 h 0.06055 A 12,20 0 0 1 32,20.900391 H 156.64648 A 12,20 0 0 1 160,20 Z" />
<path
id="path2600"
style="opacity:0.2;fill:#000000;stroke-width:0.470751"
d="M 20.033203,39.5 C 20.020878,39.666428 20.00981,39.833108 20,40 c 0,11.045695 5.372583,20 12,20 h 128 c -1.1985,-0.03799 -2.38682,-0.375069 -3.52539,-1 H 32 C 25.500897,58.973414 20.195661,50.328485 20.033203,39.5 Z"
sodipodi:nodetypes="ccscccc" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Tag"
style="display:inline">
<path
id="path1772"
style="opacity:0.2;fill:#000000;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter1774)"
d="M 91.53554,89.53554 C 89.41422,91.65686 88,94.48528 88,97.31371 v 16.97056 c 0,4.24264 0,5.65686 2.82843,8.48529 l 42.42641,42.42639 c 5.65685,5.65686 11.31371,5.65686 16.97056,0 l 16.97056,-16.97056 c 5.65686,-5.65686 5.65686,-11.3137 0,-16.97056 l -42.4264,-42.4264 C 121.94113,86 120.52692,86 116.28428,86 H 99.31371 c -2.82842,0 -5.65685,1.41422 -7.77817,3.53554 z m 4.94975,4.94974 a 8,8 0 0 1 11.3137,0 8,8 0 0 1 0,11.31371 8,8 0 0 1 -11.3137,0 8,8 0 0 1 0,-11.31371 z" />
<path
id="path7522"
style="fill:#d84315;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
d="M 91.53554,89.53554 C 89.41422,91.65686 88,94.48528 88,97.31371 v 16.97056 c 0,4.24264 0,5.65686 2.82843,8.48529 l 42.42641,42.42639 c 5.65685,5.65686 11.31371,5.65686 16.97056,0 l 16.97056,-16.97056 c 5.65686,-5.65686 5.65686,-11.3137 0,-16.97056 l -42.4264,-42.4264 C 121.94113,86 120.52692,86 116.28428,86 H 99.31371 c -2.82842,0 -5.65685,1.41422 -7.77817,3.53554 z m 4.94975,4.94974 a 8,8 0 0 1 11.3137,0 8,8 0 0 1 0,11.31371 8,8 0 0 1 -11.3137,0 8,8 0 0 1 0,-11.31371 z" />
<path
style="opacity:0.2;fill:#000000;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 88,113.28516 v 1 c 0,4.24264 -3.05e-4,5.65594 2.828125,8.48437 l 42.425785,42.42578 c 5.65685,5.65686 11.31385,5.65686 16.9707,0 l 16.9707,-16.9707 c 2.99512,-2.99511 4.3894,-5.98927 4.21289,-8.98438 -0.15686,2.66175 -1.55114,5.32263 -4.21289,7.98438 l -16.9707,16.9707 c -5.65685,5.65686 -11.31385,5.65686 -16.9707,0 L 90.828125,121.76953 C 87.999695,118.9411 88,117.5278 88,113.28516 Z"
id="path3326" />
<path
style="opacity:0.2;fill:#ffffff;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 100.89062,91.240234 a 8,8 0 0 0 -4.406245,2.244141 8,8 0 0 0 -2.244141,5.40625 8,8 0 0 1 2.244141,-4.40625 8,8 0 0 1 11.314455,0 8,8 0 0 1 2.31445,5.158203 8,8 0 0 0 -2.31445,-6.158203 8,8 0 0 0 -6.90821,-2.244141 z"
id="path3103" />
<path
style="opacity:0.2;fill:#000000;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 110.11328,100.64258 a 8,8 0 0 1 -2.31445,5.15625 8,8 0 0 1 -11.314455,0 8,8 0 0 1 -2.244141,-4.4043 8,8 0 0 0 2.244141,5.4043 8,8 0 0 0 11.314455,0 8,8 0 0 0 2.31445,-6.15625 z"
id="path3389" />
<path
style="opacity:0.2;fill:#ffffff;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 99.314453,86 c -2.82842,0 -5.657977,1.413836 -7.779297,3.535156 C 89.413836,91.656476 88,94.486023 88,97.314453 v 1 c 0,-2.82843 1.413836,-5.657977 3.535156,-7.779297 C 93.656476,88.413836 96.486033,87 99.314453,87 h 16.970707 c 4.24264,0 5.65594,-3.05e-4 8.48437,2.828125 l 42.42578,42.425785 c 2.66175,2.66174 4.05603,5.32458 4.21289,7.98632 0.17651,-2.9951 -1.21777,-5.99121 -4.21289,-8.98632 L 124.76953,88.828125 C 121.9411,85.999695 120.5278,86 116.28516,86 Z"
id="path3346" />
</g>
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="Finish"
sodipodi:insensitive="true"
style="display:inline">
<path
id="path3525"
style="fill:url(#linearGradient3588);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 32,20 A 12,20 0 0 0 20,40 12,20 0 0 0 32,60 v 100 c 0,8 4,12 12,12 27.15145,0 48.786559,-12.88889 74.77539,-21.2832 l 14.47852,14.47851 c 5.65685,5.65686 11.31385,5.65686 16.9707,0 l 16.9707,-16.9707 c 3.49839,-3.49839 4.82865,-6.99576 4,-10.49414 C 171.74927,136.15451 172,134.25875 172,132 V 44 h -0.26758 A 12,20 0 0 0 172,40 12,20 0 0 0 160,20 Z m 125.57812,0.455078 a 12,20 0 0 0 -0.38867,0.128906 12,20 0 0 1 0.38867,-0.128906 z m -2.58984,1.410156 a 12,20 0 0 0 -0.4043,0.333985 12,20 0 0 1 0.4043,-0.333985 z m -2.40039,2.423828 a 12,20 0 0 0 -0.21484,0.314454 12,20 0 0 1 0.21484,-0.314454 z m -1.82031,2.990235 a 12,20 0 0 0 -0.19727,0.373047 12,20 0 0 1 0.19727,-0.373047 z m -1.44531,3.685547 a 12,20 0 0 0 -0.20508,0.697265 12,20 0 0 1 0.20508,-0.697265 z m -0.97266,4.349609 a 12,20 0 0 0 -0.0762,0.650391 12,20 0 0 1 0.0762,-0.650391 z" />
</g>
<metadata
id="metadata1">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:title>Streamd</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Konstantin Fickel</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,6 +1,6 @@
from datetime import date, datetime, time from datetime import date, datetime, time
from streamer.localize.extract_datetime import ( from streamd.localize.extract_datetime import (
extract_date_from_marker, extract_date_from_marker,
extract_datetime_from_file_name, extract_datetime_from_file_name,
extract_datetime_from_marker, extract_datetime_from_marker,

View file

@ -1,6 +1,6 @@
import pytest import pytest
from streamer.localize.repository_configuration import ( from streamd.localize.repository_configuration import (
Dimension, Dimension,
Marker, Marker,
MarkerPlacement, MarkerPlacement,
@ -252,8 +252,8 @@ class TestMergeRepositoryConfiguration:
), ),
}, },
markers={ markers={
"Streamer": Marker( "Streamd": Marker(
display_name="Streamer", display_name="Streamd",
placements=[MarkerPlacement(dimension="project")], placements=[MarkerPlacement(dimension="project")],
) )
}, },
@ -267,8 +267,8 @@ class TestMergeRepositoryConfiguration:
), ),
}, },
markers={ markers={
"Streamer": Marker( "Streamd": Marker(
display_name="Streamer2", display_name="Streamd2",
placements=[ placements=[
MarkerPlacement( MarkerPlacement(
if_with={"Timesheet"}, dimension="timesheet", value="coding" if_with={"Timesheet"}, dimension="timesheet", value="coding"
@ -291,9 +291,9 @@ class TestMergeRepositoryConfiguration:
assert merged.dimensions["moment"].display_name == "Moment" assert merged.dimensions["moment"].display_name == "Moment"
assert merged.dimensions["timesheet"].display_name == "Timesheet" assert merged.dimensions["timesheet"].display_name == "Timesheet"
assert set(merged.markers.keys()) == {"Streamer", "JobHunting"} assert set(merged.markers.keys()) == {"Streamd", "JobHunting"}
assert merged.markers["Streamer"].display_name == "Streamer2" assert merged.markers["Streamd"].display_name == "Streamd2"
assert merged.markers["Streamer"].placements == [ assert merged.markers["Streamd"].placements == [
MarkerPlacement(dimension="project", value=None, if_with=set()), MarkerPlacement(dimension="project", value=None, if_with=set()),
MarkerPlacement( MarkerPlacement(
if_with={"Timesheet"}, dimension="timesheet", value="coding" if_with={"Timesheet"}, dimension="timesheet", value="coding"
@ -359,7 +359,9 @@ class TestMergeRepositoryConfiguration:
], ],
) )
def test_merge_repository_configuration_propagate_preserves_base_when_omitted( def test_merge_repository_configuration_propagate_preserves_base_when_omitted(
base, second, expected_propagate base: RepositoryConfiguration,
second: RepositoryConfiguration,
expected_propagate: bool,
): ):
merged = merge_repository_configuration(base, second) merged = merge_repository_configuration(base, second)
assert merged.dimensions["d"].propagate is expected_propagate assert merged.dimensions["d"].propagate is expected_propagate

View file

@ -1,6 +1,5 @@
from faker import Faker from faker import Faker
from streamd.parse import Shard, StreamFile, parse_markdown_file
from streamer.parse import Shard, StreamFile, parse_markdown_file
fake = Faker() fake = Faker()

View file

@ -2,8 +2,8 @@ from __future__ import annotations
from datetime import datetime from datetime import datetime
from streamer.localize import LocalizedShard from streamd.localize import LocalizedShard
from streamer.query.find import find_shard, find_shard_by_position from streamd.query.find import find_shard, find_shard_by_position
def generate_localized_shard( def generate_localized_shard(

View file

@ -1,14 +1,14 @@
from datetime import datetime from datetime import datetime
from streamer.localize.localize import localize_stream_file from streamd.localize.localize import localize_stream_file
from streamer.localize.localized_shard import LocalizedShard from streamd.localize.localized_shard import LocalizedShard
from streamer.localize.repository_configuration import ( from streamd.localize.repository_configuration import (
Dimension, Dimension,
Marker, Marker,
MarkerPlacement, MarkerPlacement,
RepositoryConfiguration, RepositoryConfiguration,
) )
from streamer.parse.shard import Shard, StreamFile from streamd.parse.shard import Shard, StreamFile
repository_configuration = RepositoryConfiguration( repository_configuration = RepositoryConfiguration(
dimensions={ dimensions={
@ -29,8 +29,8 @@ repository_configuration = RepositoryConfiguration(
), ),
}, },
markers={ markers={
"Streamer": Marker( "Streamd": Marker(
display_name="Streamer", display_name="Streamd",
placements=[ placements=[
MarkerPlacement(dimension="project"), MarkerPlacement(dimension="project"),
MarkerPlacement( MarkerPlacement(
@ -49,39 +49,39 @@ class TestLocalize:
def test_project_simple_stream_file(self): def test_project_simple_stream_file(self):
stream_file = StreamFile( stream_file = StreamFile(
file_name="20250622-121000 Test File.md", file_name="20250622-121000 Test File.md",
shard=Shard(start_line=1, end_line=1, markers=["Streamer"]), shard=Shard(start_line=1, end_line=1, markers=["Streamd"]),
) )
assert localize_stream_file( assert localize_stream_file(
stream_file, repository_configuration stream_file, repository_configuration
) == LocalizedShard( ) == LocalizedShard(
moment=datetime(2025, 6, 22, 12, 10, 0, 0), moment=datetime(2025, 6, 22, 12, 10, 0, 0),
markers=["Streamer"], markers=["Streamd"],
tags=[], tags=[],
start_line=1, start_line=1,
end_line=1, end_line=1,
children=[], children=[],
location={"project": "Streamer", "file": stream_file.file_name}, location={"project": "Streamd", "file": stream_file.file_name},
) )
def test_timesheet_use_case(self): def test_timesheet_use_case(self):
stream_file = StreamFile( stream_file = StreamFile(
file_name="20260131-210000 Test File.md", file_name="20260131-210000 Test File.md",
shard=Shard(start_line=1, end_line=1, markers=["Timesheet", "Streamer"]), shard=Shard(start_line=1, end_line=1, markers=["Timesheet", "Streamd"]),
) )
assert localize_stream_file( assert localize_stream_file(
stream_file, repository_configuration stream_file, repository_configuration
) == LocalizedShard( ) == LocalizedShard(
moment=datetime(2026, 1, 31, 21, 0, 0, 0), moment=datetime(2026, 1, 31, 21, 0, 0, 0),
markers=["Timesheet", "Streamer"], markers=["Timesheet", "Streamd"],
tags=[], tags=[],
start_line=1, start_line=1,
end_line=1, end_line=1,
children=[], children=[],
location={ location={
"file": stream_file.file_name, "file": stream_file.file_name,
"project": "Streamer", "project": "Streamd",
"timesheet": "coding", "timesheet": "coding",
}, },
) )

View file

@ -4,13 +4,13 @@ from datetime import datetime, time
import pytest import pytest
from streamer.localize.localized_shard import LocalizedShard from streamd.localize.localized_shard import LocalizedShard
from streamer.timesheet.configuration import ( from streamd.timesheet.configuration import (
TIMESHEET_DIMENSION_NAME, TIMESHEET_DIMENSION_NAME,
TimesheetPointType, TimesheetPointType,
) )
from streamer.timesheet.extract import extract_timesheets from streamd.timesheet.extract import extract_timesheets
from streamer.timesheet.timecard import SpecialDayType, Timecard, Timesheet from streamd.timesheet.timecard import SpecialDayType, Timecard, Timesheet
def point(at: datetime, type: TimesheetPointType) -> LocalizedShard: def point(at: datetime, type: TimesheetPointType) -> LocalizedShard:
@ -243,7 +243,7 @@ class TestExtractTimesheets:
] ]
with pytest.raises(ValueError, match=r"Last Timecard of .* is not a break"): with pytest.raises(ValueError, match=r"Last Timecard of .* is not a break"):
extract_timesheets(shards) _ = extract_timesheets(shards)
def test_two_special_day_types_same_day_is_invalid(self): def test_two_special_day_types_same_day_is_invalid(self):
""" """
@ -257,7 +257,7 @@ class TestExtractTimesheets:
] ]
with pytest.raises(ValueError, match=r"is both .* and .*"): with pytest.raises(ValueError, match=r"is both .* and .*"):
extract_timesheets(shards) _ = extract_timesheets(shards)
def test_points_with_mixed_dates_inside_one_group_raises(self): def test_points_with_mixed_dates_inside_one_group_raises(self):
""" """
@ -273,7 +273,7 @@ class TestExtractTimesheets:
] ]
with pytest.raises(ValueError, match=r"Last Timecard of .* is not a break"): with pytest.raises(ValueError, match=r"Last Timecard of .* is not a break"):
extract_timesheets(shards) _ = extract_timesheets(shards)
def test_day_with_only_breaks_is_ignored(self): def test_day_with_only_breaks_is_ignored(self):
""" """

148
uv.lock generated
View file

@ -1,6 +1,6 @@
version = 1 version = 1
revision = 3 revision = 3
requires-python = ">=3.12" requires-python = ">=3.13"
[[package]] [[package]]
name = "annotated-doc" name = "annotated-doc"
@ -20,6 +20,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
] ]
[[package]]
name = "basedpyright"
version = "1.38.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nodejs-wheel-binaries" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e4/a3/20aa7c4e83f2f614e0036300f3c352775dede0655c66814da16c37b661a9/basedpyright-1.38.2.tar.gz", hash = "sha256:b433b2b8ba745ed7520cdc79a29a03682f3fb00346d272ece5944e9e5e5daa92", size = 25277019, upload-time = "2026-02-26T11:18:43.594Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ac/12/736cab83626fea3fe65cdafb3ef3d2ee9480c56723f2fd33921537289a5e/basedpyright-1.38.2-py3-none-any.whl", hash = "sha256:153481d37fd19f9e3adedc8629d1d071b10c5f5e49321fb026b74444b7c70e24", size = 12312475, upload-time = "2026-02-26T11:18:40.373Z" },
]
[[package]] [[package]]
name = "click" name = "click"
version = "8.3.1" version = "8.3.1"
@ -43,14 +55,14 @@ wheels = [
[[package]] [[package]]
name = "faker" name = "faker"
version = "40.4.0" version = "40.11.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "tzdata", marker = "sys_platform == 'win32'" }, { name = "tzdata", marker = "sys_platform == 'win32'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/fc/7e/dccb7013c9f3d66f2e379383600629fec75e4da2698548bdbf2041ea4b51/faker-40.4.0.tar.gz", hash = "sha256:76f8e74a3df28c3e2ec2caafa956e19e37a132fdc7ea067bc41783affcfee364", size = 1952221, upload-time = "2026-02-06T23:30:15.515Z" } sdist = { url = "https://files.pythonhosted.org/packages/94/dc/b68e5378e5a7db0ab776efcdd53b6fe374b29d703e156fd5bb4c5437069e/faker-40.11.0.tar.gz", hash = "sha256:7c419299103b13126bd02ec14bd2b47b946edb5a5eedf305e66a193b25f9a734", size = 1957570, upload-time = "2026-03-13T14:36:11.844Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/ac/63/58efa67c10fb27810d34351b7a10f85f109a7f7e2a07dc3773952459c47b/faker-40.4.0-py3-none-any.whl", hash = "sha256:486d43c67ebbb136bc932406418744f9a0bdf2c07f77703ea78b58b77e9aa443", size = 1987060, upload-time = "2026-02-06T23:30:13.44Z" }, { url = "https://files.pythonhosted.org/packages/b1/fa/a86c6ba66f0308c95b9288b1e3eaccd934b545646f63494a86f1ec2f8c8e/faker-40.11.0-py3-none-any.whl", hash = "sha256:0e9816c950528d2a37d74863f3ef389ea9a3a936cbcde0b11b8499942e25bf90", size = 1989457, upload-time = "2026-03-13T14:36:09.792Z" },
] ]
[[package]] [[package]]
@ -93,12 +105,19 @@ wheels = [
] ]
[[package]] [[package]]
name = "nodeenv" name = "nodejs-wheel-binaries"
version = "1.10.0" version = "24.13.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } sdist = { url = "https://files.pythonhosted.org/packages/e5/d0/81d98b8fddc45332f79d6ad5749b1c7409fb18723545eae75d9b7e0048fb/nodejs_wheel_binaries-24.13.1.tar.gz", hash = "sha256:512659a67449a038231e2e972d49e77049d2cf789ae27db39eff4ab1ca52ac57", size = 8056, upload-time = "2026-02-12T17:31:04.368Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, { url = "https://files.pythonhosted.org/packages/aa/04/1ffe1838306654fcb50bcf46172567d50c8e27a76f4b9e55a1971fab5c4f/nodejs_wheel_binaries-24.13.1-py2.py3-none-macosx_13_0_arm64.whl", hash = "sha256:360ac9382c651de294c23c4933a02358c4e11331294983f3cf50ca1ac32666b1", size = 54757440, upload-time = "2026-02-12T17:30:35.748Z" },
{ url = "https://files.pythonhosted.org/packages/66/f6/81ad81bc3bd919a20b110130c4fd318c7b6a5abb37eb53daa353ad908012/nodejs_wheel_binaries-24.13.1-py2.py3-none-macosx_13_0_x86_64.whl", hash = "sha256:035b718946793986762cdd50deee7f5f1a8f1b0bad0f0cfd57cad5492f5ea018", size = 54932957, upload-time = "2026-02-12T17:30:40.114Z" },
{ url = "https://files.pythonhosted.org/packages/14/be/8e8a2bd50953c4c5b7e0fca07368d287917b84054dc3c93dd26a2940f0f9/nodejs_wheel_binaries-24.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:f795e9238438c4225f76fbd01e2b8e1a322116bbd0dc15a7dbd585a3ad97961e", size = 59287257, upload-time = "2026-02-12T17:30:43.781Z" },
{ url = "https://files.pythonhosted.org/packages/58/57/92f6dfa40647702a9fa6d32393ce4595d0fc03c1daa9b245df66cc60e959/nodejs_wheel_binaries-24.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:978328e3ad522571eb163b042dfbd7518187a13968fe372738f90fdfe8a46afc", size = 59781783, upload-time = "2026-02-12T17:30:47.387Z" },
{ url = "https://files.pythonhosted.org/packages/f7/a5/457b984cf675cf86ace7903204b9c36edf7a2d1b4325ddf71eaf8d1027c7/nodejs_wheel_binaries-24.13.1-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e1dc893df85299420cd2a5feea0c3f8482a719b5f7f82d5977d58718b8b78b5f", size = 61287166, upload-time = "2026-02-12T17:30:50.646Z" },
{ url = "https://files.pythonhosted.org/packages/3c/99/da515f7bc3bce35cfa6005f0e0c4e3c4042a466782b143112eb393b663be/nodejs_wheel_binaries-24.13.1-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0e581ae219a39073dcadd398a2eb648f0707b0f5d68c565586139f919c91cbe9", size = 61870142, upload-time = "2026-02-12T17:30:54.563Z" },
{ url = "https://files.pythonhosted.org/packages/cc/c0/22001d2c96d8200834af7d1de5e72daa3266c7270330275104c3d9ddd143/nodejs_wheel_binaries-24.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:d4c969ea0bcb8c8b20bc6a7b4ad2796146d820278f17d4dc20229b088c833e22", size = 41185473, upload-time = "2026-02-12T17:30:57.524Z" },
{ url = "https://files.pythonhosted.org/packages/ab/c4/7532325f968ecfc078e8a028e69a52e4c3f95fb800906bf6931ac1e89e2b/nodejs_wheel_binaries-24.13.1-py2.py3-none-win_arm64.whl", hash = "sha256:caec398cb9e94c560bacdcba56b3828df22a355749eb291f47431af88cbf26dc", size = 38881194, upload-time = "2026-02-12T17:31:00.214Z" },
] ]
[[package]] [[package]]
@ -143,20 +162,6 @@ dependencies = [
] ]
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
{ url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
{ url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
{ url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
{ url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
{ url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
{ url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
{ url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
{ url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
{ url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
{ url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
{ url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
{ url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
{ url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
{ url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
{ url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
{ url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
@ -199,24 +204,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
{ url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
{ url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
{ url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
{ url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
] ]
[[package]] [[package]]
name = "pydantic-settings" name = "pydantic-settings"
version = "2.12.0" version = "2.13.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "pydantic" }, { name = "pydantic" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "typing-inspection" }, { name = "typing-inspection" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" },
] ]
[package.optional-dependencies] [package.optional-dependencies]
@ -233,19 +234,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
] ]
[[package]]
name = "pyright"
version = "1.1.408"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nodeenv" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/74/b2/5db700e52554b8f025faa9c3c624c59f1f6c8841ba81ab97641b54322f16/pyright-1.1.408.tar.gz", hash = "sha256:f28f2321f96852fa50b5829ea492f6adb0e6954568d1caa3f3af3a5f555eb684", size = 4400578, upload-time = "2026-01-08T08:07:38.795Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl", hash = "sha256:090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1", size = 6399144, upload-time = "2026-01-08T08:07:37.082Z" },
]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "9.0.2" version = "9.0.2"
@ -277,16 +265,6 @@ version = "6.0.3"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
@ -319,40 +297,40 @@ wheels = [
[[package]] [[package]]
name = "rich" name = "rich"
version = "14.3.2" version = "14.3.3"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "markdown-it-py" }, { name = "markdown-it-py" },
{ name = "pygments" }, { 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" } sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" }
wheels = [ 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" }, { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" },
] ]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.15.0" version = "0.15.6"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" },
{ url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" },
{ url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" },
{ url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" },
{ url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" },
{ url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" },
{ url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" },
{ url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" },
{ url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" },
{ url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" },
{ url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" },
{ url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" },
{ url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" },
{ url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" },
{ url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" },
{ url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" },
{ url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" },
] ]
[[package]] [[package]]
@ -365,7 +343,7 @@ wheels = [
] ]
[[package]] [[package]]
name = "streamer" name = "streamd"
version = "0.1.0" version = "0.1.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
@ -380,8 +358,8 @@ dependencies = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "basedpyright" },
{ name = "faker" }, { name = "faker" },
{ name = "pyright" },
{ name = "pytest" }, { name = "pytest" },
{ name = "ruff" }, { name = "ruff" },
] ]
@ -391,23 +369,23 @@ requires-dist = [
{ name = "click", specifier = "==8.3.1" }, { name = "click", specifier = "==8.3.1" },
{ name = "mistletoe", specifier = "==1.5.1" }, { name = "mistletoe", specifier = "==1.5.1" },
{ name = "pydantic", specifier = "==2.12.5" }, { name = "pydantic", specifier = "==2.12.5" },
{ name = "pydantic-settings", extras = ["yaml"], specifier = "==2.12.0" }, { name = "pydantic-settings", extras = ["yaml"], specifier = "==2.13.1" },
{ name = "rich", specifier = "==14.3.2" }, { name = "rich", specifier = "==14.3.3" },
{ name = "typer", specifier = "==0.21.2" }, { name = "typer", specifier = "==0.24.1" },
{ name = "xdg-base-dirs", specifier = "==6.0.2" }, { name = "xdg-base-dirs", specifier = "==6.0.2" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "faker", specifier = "==40.4.0" }, { name = "basedpyright", specifier = "==1.38.2" },
{ name = "pyright", specifier = "==1.1.408" }, { name = "faker", specifier = "==40.11.0" },
{ name = "pytest", specifier = "==9.0.2" }, { name = "pytest", specifier = "==9.0.2" },
{ name = "ruff", specifier = "==0.15.0" }, { name = "ruff", specifier = "==0.15.6" },
] ]
[[package]] [[package]]
name = "typer" name = "typer"
version = "0.21.2" version = "0.24.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "annotated-doc" }, { name = "annotated-doc" },
@ -415,9 +393,9 @@ dependencies = [
{ name = "rich" }, { name = "rich" },
{ name = "shellingham" }, { name = "shellingham" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/f2/1e/a27cc02a0cd715118c71fa2aef2c687fdefc3c28d90fd0dd789c5118154c/typer-0.21.2.tar.gz", hash = "sha256:1abd95a3b675e17ff61b0838ac637fe9478d446d62ad17fa4bb81ea57cc54028", size = 120426, upload-time = "2026-02-10T19:33:46.182Z" } sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/b8/cc/d59f893fbdfb5f58770c05febfc4086a46875f1084453621c35605cec946/typer-0.21.2-py3-none-any.whl", hash = "sha256:c3d8de54d00347ef90b82131ca946274f017cffb46683ae3883c360fa958f55c", size = 56728, upload-time = "2026-02-10T19:33:48.01Z" }, { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" },
] ]
[[package]] [[package]]