pyintake 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. pyintake-0.0.1/.github/workflows/publish.yml +70 -0
  2. pyintake-0.0.1/.github/workflows/test.yml +39 -0
  3. pyintake-0.0.1/Makefile +110 -0
  4. pyintake-0.0.1/PKG-INFO +47 -0
  5. pyintake-0.0.1/pyproject.toml +176 -0
  6. pyintake-0.0.1/setup.cfg +4 -0
  7. pyintake-0.0.1/src/pyintake/__init__.py +83 -0
  8. pyintake-0.0.1/src/pyintake/__pycache__/__init__.cpython-313.pyc +0 -0
  9. pyintake-0.0.1/src/pyintake/__pycache__/_protocols.cpython-313.pyc +0 -0
  10. pyintake-0.0.1/src/pyintake/__pycache__/_types.cpython-313.pyc +0 -0
  11. pyintake-0.0.1/src/pyintake/__pycache__/errors.cpython-313.pyc +0 -0
  12. pyintake-0.0.1/src/pyintake/__pycache__/event.cpython-313.pyc +0 -0
  13. pyintake-0.0.1/src/pyintake/_protocols.py +145 -0
  14. pyintake-0.0.1/src/pyintake/_types.py +135 -0
  15. pyintake-0.0.1/src/pyintake/adapters/__init__.py +6 -0
  16. pyintake-0.0.1/src/pyintake/api/__init__.py +3 -0
  17. pyintake-0.0.1/src/pyintake/api/routes/__init__.py +3 -0
  18. pyintake-0.0.1/src/pyintake/api/routes/v1/__init__.py +3 -0
  19. pyintake-0.0.1/src/pyintake/buffer/__init__.py +6 -0
  20. pyintake-0.0.1/src/pyintake/buffer/__pycache__/__init__.cpython-313.pyc +0 -0
  21. pyintake-0.0.1/src/pyintake/buffer/__pycache__/_dedup.cpython-313.pyc +0 -0
  22. pyintake-0.0.1/src/pyintake/buffer/__pycache__/_priority_buffer.cpython-313.pyc +0 -0
  23. pyintake-0.0.1/src/pyintake/buffer/_dedup.py +110 -0
  24. pyintake-0.0.1/src/pyintake/buffer/_priority_buffer.py +191 -0
  25. pyintake-0.0.1/src/pyintake/config/__init__.py +3 -0
  26. pyintake-0.0.1/src/pyintake/core/__init__.py +6 -0
  27. pyintake-0.0.1/src/pyintake/core/__pycache__/__init__.cpython-313.pyc +0 -0
  28. pyintake-0.0.1/src/pyintake/core/__pycache__/_drain.cpython-313.pyc +0 -0
  29. pyintake-0.0.1/src/pyintake/core/__pycache__/_engine.cpython-313.pyc +0 -0
  30. pyintake-0.0.1/src/pyintake/core/__pycache__/_rate_limiter.cpython-313.pyc +0 -0
  31. pyintake-0.0.1/src/pyintake/core/_drain.py +248 -0
  32. pyintake-0.0.1/src/pyintake/core/_engine.py +248 -0
  33. pyintake-0.0.1/src/pyintake/core/_rate_limiter.py +87 -0
  34. pyintake-0.0.1/src/pyintake/errors.py +205 -0
  35. pyintake-0.0.1/src/pyintake/event.py +244 -0
  36. pyintake-0.0.1/src/pyintake/metrics/__init__.py +7 -0
  37. pyintake-0.0.1/src/pyintake/metrics/__pycache__/__init__.cpython-313.pyc +0 -0
  38. pyintake-0.0.1/src/pyintake/metrics/__pycache__/_collector.cpython-313.pyc +0 -0
  39. pyintake-0.0.1/src/pyintake/metrics/__pycache__/_models.cpython-313.pyc +0 -0
  40. pyintake-0.0.1/src/pyintake/metrics/_collector.py +196 -0
  41. pyintake-0.0.1/src/pyintake/metrics/_models.py +85 -0
  42. pyintake-0.0.1/src/pyintake/py.typed +0 -0
  43. pyintake-0.0.1/src/pyintake/validation/__init__.py +6 -0
  44. pyintake-0.0.1/src/pyintake/validation/__pycache__/__init__.cpython-313.pyc +0 -0
  45. pyintake-0.0.1/src/pyintake/validation/__pycache__/_chain.cpython-313.pyc +0 -0
  46. pyintake-0.0.1/src/pyintake/validation/__pycache__/_validators.cpython-313.pyc +0 -0
  47. pyintake-0.0.1/src/pyintake/validation/_chain.py +82 -0
  48. pyintake-0.0.1/src/pyintake/validation/_validators.py +181 -0
  49. pyintake-0.0.1/src/pyintake.egg-info/PKG-INFO +47 -0
  50. pyintake-0.0.1/src/pyintake.egg-info/SOURCES.txt +77 -0
  51. pyintake-0.0.1/src/pyintake.egg-info/dependency_links.txt +1 -0
  52. pyintake-0.0.1/src/pyintake.egg-info/entry_points.txt +2 -0
  53. pyintake-0.0.1/src/pyintake.egg-info/requires.txt +33 -0
  54. pyintake-0.0.1/src/pyintake.egg-info/top_level.txt +1 -0
  55. pyintake-0.0.1/tests/__init__.py +0 -0
  56. pyintake-0.0.1/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  57. pyintake-0.0.1/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc +0 -0
  58. pyintake-0.0.1/tests/conftest.py +116 -0
  59. pyintake-0.0.1/tests/integration/__init__.py +0 -0
  60. pyintake-0.0.1/tests/unit/__init__.py +0 -0
  61. pyintake-0.0.1/tests/unit/__pycache__/__init__.cpython-313.pyc +0 -0
  62. pyintake-0.0.1/tests/unit/__pycache__/test_dedup.cpython-313-pytest-9.0.2.pyc +0 -0
  63. pyintake-0.0.1/tests/unit/__pycache__/test_drain.cpython-313-pytest-9.0.2.pyc +0 -0
  64. pyintake-0.0.1/tests/unit/__pycache__/test_engine.cpython-313-pytest-9.0.2.pyc +0 -0
  65. pyintake-0.0.1/tests/unit/__pycache__/test_event.cpython-313-pytest-9.0.2.pyc +0 -0
  66. pyintake-0.0.1/tests/unit/__pycache__/test_metrics.cpython-313-pytest-9.0.2.pyc +0 -0
  67. pyintake-0.0.1/tests/unit/__pycache__/test_priority_buffer.cpython-313-pytest-9.0.2.pyc +0 -0
  68. pyintake-0.0.1/tests/unit/__pycache__/test_rate_limiter.cpython-313-pytest-9.0.2.pyc +0 -0
  69. pyintake-0.0.1/tests/unit/__pycache__/test_validation_chain.cpython-313-pytest-9.0.2.pyc +0 -0
  70. pyintake-0.0.1/tests/unit/__pycache__/test_validators.cpython-313-pytest-9.0.2.pyc +0 -0
  71. pyintake-0.0.1/tests/unit/test_dedup.py +135 -0
  72. pyintake-0.0.1/tests/unit/test_drain.py +338 -0
  73. pyintake-0.0.1/tests/unit/test_engine.py +228 -0
  74. pyintake-0.0.1/tests/unit/test_event.py +301 -0
  75. pyintake-0.0.1/tests/unit/test_metrics.py +215 -0
  76. pyintake-0.0.1/tests/unit/test_priority_buffer.py +224 -0
  77. pyintake-0.0.1/tests/unit/test_rate_limiter.py +103 -0
  78. pyintake-0.0.1/tests/unit/test_validation_chain.py +167 -0
  79. pyintake-0.0.1/tests/unit/test_validators.py +200 -0
@@ -0,0 +1,70 @@
1
+ # Build and publish to PyPI when a version tag (v*) is pushed.
2
+ # Also supports manual run (workflow_dispatch).
3
+ # Version is derived from the git tag by setuptools-scm.
4
+
5
+ name: Build and Publish
6
+
7
+ on:
8
+ push:
9
+ tags: ['v*']
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ id-token: write
14
+ contents: write
15
+
16
+ jobs:
17
+ build-and-publish:
18
+ name: Build and Publish to PyPI
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v4
23
+ with:
24
+ fetch-depth: 0
25
+ ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.ref }}
26
+
27
+ - name: Set version from tag
28
+ if: startsWith(github.ref, 'refs/tags/')
29
+ run: echo "SETUPTOOLS_SCM_PRETEND_VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
30
+
31
+ - name: Set up Python
32
+ uses: actions/setup-python@v5
33
+ with:
34
+ python-version: '3.12'
35
+ cache: 'pip'
36
+ cache-dependency-path: pyproject.toml
37
+
38
+ - name: Install dependencies and run tests
39
+ run: |
40
+ python -m pip install --upgrade pip
41
+ pip install -e ".[dev]"
42
+ pytest
43
+
44
+ - name: Install build tools
45
+ run: pip install build
46
+
47
+ - name: Build package
48
+ run: python -m build
49
+
50
+ - name: Verify package
51
+ run: |
52
+ python -m zipfile -l dist/pyintake-*.whl | head -30
53
+ echo "Package built successfully"
54
+
55
+ # Only publish to PyPI when building from a tag (official release). Skip for workflow_dispatch so we don't publish dev versions as pre-release.
56
+ - name: Publish to PyPI
57
+ if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'release'
58
+ uses: pypa/gh-action-pypi-publish@release/v1
59
+ with:
60
+ packages-dir: dist/
61
+
62
+ - name: Create GitHub Release
63
+ if: startsWith(github.ref, 'refs/tags/') && github.event_name != 'release'
64
+ uses: softprops/action-gh-release@v2.4.2
65
+ with:
66
+ tag_name: ${{ github.ref_name }}
67
+ name: ${{ github.ref_name }}
68
+ generate_release_notes: true
69
+ env:
70
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,39 @@
1
+ name: Test
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main, develop]
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ python-version: ['3.11', '3.12', '3.13']
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python ${{ matrix.python-version }}
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ cache: 'pip'
23
+ cache-dependency-path: pyproject.toml
24
+
25
+ - name: Install dependencies
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ pip install -e ".[dev]"
29
+
30
+ - name: Run tests
31
+ run: pytest
32
+
33
+ - name: Run linting
34
+ run: |
35
+ ruff format --check src/pyintake tests
36
+ ruff check src/pyintake tests
37
+
38
+ - name: Run type checking
39
+ run: mypy src/pyintake || true
@@ -0,0 +1,110 @@
1
+ .PHONY: help install install-dev test test-verbose test-cov test-fast test-unit test-integration clean build format lint type-check check publish-test publish docs docs-serve
2
+
3
+ # Detect if venv exists and set PYTHON/PIP accordingly
4
+ ifeq ($(wildcard venv/bin/python),)
5
+ PYTHON := python3
6
+ PIP := pip3
7
+ PYTHON_CMD := python3 -m
8
+ else
9
+ PYTHON := venv/bin/python
10
+ PIP := venv/bin/pip
11
+ PYTHON_CMD := venv/bin/python -m
12
+ endif
13
+
14
+ help: ## Show this help message
15
+ @echo "Available commands:"
16
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
17
+ @echo ""
18
+ @echo "Publishing:"
19
+ @echo " For production: Create a GitHub Release (automatically publishes via Trusted Publishing)"
20
+
21
+ install: ## Install the package in development mode
22
+ python3 -m venv venv
23
+ . venv/bin/activate && pip install --upgrade pip && pip install -e .
24
+
25
+ install-dev: ## Install package + all development dependencies
26
+ python3 -m venv venv
27
+ . venv/bin/activate && pip install --upgrade pip && pip install -e ".[dev]"
28
+ @echo "To activate: source venv/bin/activate"
29
+
30
+ test: ## Run tests
31
+ $(PYTHON_CMD) pytest
32
+
33
+ test-verbose: ## Run tests with verbose output
34
+ $(PYTHON_CMD) pytest -v
35
+
36
+ test-cov: ## Run tests with coverage
37
+ $(PYTHON_CMD) pytest --cov=pyintake --cov-report=html --cov-report=term
38
+
39
+ test-fast: ## Run fast tests only (exclude slow markers)
40
+ $(PYTHON_CMD) pytest -m "not slow"
41
+
42
+ test-unit: ## Run unit tests only
43
+ $(PYTHON_CMD) pytest -m "unit"
44
+
45
+ test-integration: ## Run integration tests only
46
+ $(PYTHON_CMD) pytest -m "integration"
47
+
48
+ clean: ## Clean build artifacts
49
+ rm -rf build/ dist/ *.egg-info src/*.egg-info src/pyintake.egg-info
50
+ rm -rf .pytest_cache .mypy_cache .ruff_cache htmlcov/ .coverage
51
+ find . -type d -name __pycache__ -exec rm -r {} + 2>/dev/null || true
52
+ find . -type f -name "*.pyc" -delete 2>/dev/null || true
53
+
54
+ build: ## Build distribution packages
55
+ @if ! $(PYTHON) -c "import build" 2>/dev/null; then \
56
+ echo "Installing build..."; \
57
+ $(PIP) install build; \
58
+ fi
59
+ $(PYTHON_CMD) build
60
+
61
+ format: ## Format code with ruff
62
+ $(PYTHON_CMD) ruff format src/pyintake tests
63
+ $(PYTHON_CMD) ruff check --fix src/pyintake tests
64
+
65
+ lint: ## Run linting checks (same as CI)
66
+ $(PYTHON_CMD) ruff format --check src/pyintake tests
67
+ $(PYTHON_CMD) ruff check src/pyintake tests
68
+
69
+ type-check: ## Run type checking with mypy
70
+ $(PYTHON_CMD) mypy src/pyintake
71
+
72
+ check: format lint type-check test ## Run all checks (format, lint, type-check, test)
73
+
74
+ docs: ## Build documentation
75
+ $(PYTHON_CMD) mkdocs build
76
+
77
+ docs-serve: ## Serve documentation locally
78
+ $(PYTHON_CMD) mkdocs serve
79
+
80
+ publish-test: clean build ## Build and upload to TestPyPI (requires TWINE_PASSWORD)
81
+ @echo "Uploading to TestPyPI..."
82
+ @if ! $(PYTHON) -c "import twine" 2>/dev/null; then \
83
+ echo "Installing twine..."; \
84
+ $(PIP) install twine; \
85
+ fi
86
+ @if [ -z "$$TWINE_PASSWORD" ]; then \
87
+ echo "Warning: TWINE_PASSWORD not set. Set it with:"; \
88
+ echo " export TWINE_PASSWORD=your-testpypi-token"; \
89
+ exit 1; \
90
+ fi
91
+ $(PYTHON_CMD) twine upload --repository testpypi dist/*
92
+ @echo "Published to TestPyPI"
93
+ @echo "Test install: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ pyintake"
94
+
95
+ publish: clean build ## Build and upload to PyPI (use GitHub Releases for production)
96
+ @echo "WARNING: This will publish to PyPI!"
97
+ @echo "For production releases, use GitHub Releases instead (automatic via Trusted Publishing)"
98
+ @echo "Press Ctrl+C to cancel, or Enter to continue..."
99
+ @read dummy
100
+ @if ! $(PYTHON) -c "import twine" 2>/dev/null; then \
101
+ echo "Installing twine..."; \
102
+ $(PIP) install twine; \
103
+ fi
104
+ @if [ -z "$$TWINE_PASSWORD" ]; then \
105
+ echo "Warning: TWINE_PASSWORD not set. Set it with:"; \
106
+ echo " export TWINE_PASSWORD=your-pypi-token"; \
107
+ exit 1; \
108
+ fi
109
+ $(PYTHON_CMD) twine upload dist/*
110
+ @echo "Published to PyPI"
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyintake
3
+ Version: 0.0.1
4
+ Summary: Transport-agnostic data ingestion system with priority buffering and backpressure
5
+ Author-email: StatFYI <contact@statfyi.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/optophi/pyintake
8
+ Project-URL: Repository, https://github.com/optophi/pyintake
9
+ Project-URL: Issues, https://github.com/optophi/pyintake/issues
10
+ Keywords: ingestion,data-pipeline,buffering,backpressure,event-driven
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Typing :: Typed
19
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ Provides-Extra: http
23
+ Requires-Dist: fastapi>=0.104.0; extra == "http"
24
+ Requires-Dist: uvicorn[standard]>=0.24.0; extra == "http"
25
+ Requires-Dist: pydantic>=2.0.0; extra == "http"
26
+ Provides-Extra: kafka
27
+ Requires-Dist: aiokafka>=0.10.0; extra == "kafka"
28
+ Provides-Extra: websocket
29
+ Requires-Dist: websockets>=12.0; extra == "websocket"
30
+ Provides-Extra: api
31
+ Requires-Dist: pyintake[http]; extra == "api"
32
+ Requires-Dist: pydantic-settings>=2.0.0; extra == "api"
33
+ Provides-Extra: config
34
+ Requires-Dist: pyyaml>=6.0.0; extra == "config"
35
+ Requires-Dist: pydantic>=2.0.0; extra == "config"
36
+ Provides-Extra: all
37
+ Requires-Dist: pyintake[api,config,kafka,websocket]; extra == "all"
38
+ Provides-Extra: dev
39
+ Requires-Dist: pyintake[all]; extra == "dev"
40
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
41
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
42
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
43
+ Requires-Dist: ruff>=0.9.0; extra == "dev"
44
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
45
+ Requires-Dist: pre-commit>=3.0.0; extra == "dev"
46
+ Requires-Dist: httpx>=0.24.0; extra == "dev"
47
+ Requires-Dist: build>=0.10.0; extra == "dev"
@@ -0,0 +1,176 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel", "setuptools-scm>=8.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pyintake"
7
+ dynamic = ["version"]
8
+ description = "Transport-agnostic data ingestion system with priority buffering and backpressure"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ dependencies = []
13
+ authors = [
14
+ { name = "StatFYI", email = "contact@statfyi.com" },
15
+ ]
16
+ keywords = [
17
+ "ingestion",
18
+ "data-pipeline",
19
+ "buffering",
20
+ "backpressure",
21
+ "event-driven",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 3 - Alpha",
25
+ "Intended Audience :: Developers",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Typing :: Typed",
32
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/optophi/pyintake"
37
+ Repository = "https://github.com/optophi/pyintake"
38
+ Issues = "https://github.com/optophi/pyintake/issues"
39
+
40
+ [tool.setuptools_scm]
41
+ local_scheme = "no-local-version"
42
+
43
+ [project.optional-dependencies]
44
+ # ---------------------------------------------------------------------------
45
+ # Install extras:
46
+ # pip install pyintake -> core library only (zero deps)
47
+ # pip install pyintake[http] -> + FastAPI HTTP adapter
48
+ # pip install pyintake[kafka] -> + aiokafka Kafka adapter
49
+ # pip install pyintake[websocket] -> + websockets adapter
50
+ # pip install pyintake[api] -> + REST API server
51
+ # pip install pyintake[all] -> api + kafka + websocket
52
+ # pip install pyintake[dev] -> all + testing & dev tools
53
+ # ---------------------------------------------------------------------------
54
+
55
+ http = [
56
+ "fastapi>=0.104.0",
57
+ "uvicorn[standard]>=0.24.0",
58
+ "pydantic>=2.0.0",
59
+ ]
60
+ kafka = [
61
+ "aiokafka>=0.10.0",
62
+ ]
63
+ websocket = [
64
+ "websockets>=12.0",
65
+ ]
66
+ api = [
67
+ "pyintake[http]",
68
+ "pydantic-settings>=2.0.0",
69
+ ]
70
+ config = [
71
+ "pyyaml>=6.0.0",
72
+ "pydantic>=2.0.0",
73
+ ]
74
+ all = [
75
+ "pyintake[api,kafka,websocket,config]",
76
+ ]
77
+ dev = [
78
+ "pyintake[all]",
79
+ "pytest>=7.0.0",
80
+ "pytest-asyncio>=0.21.0",
81
+ "pytest-cov>=4.0.0",
82
+ "ruff>=0.9.0",
83
+ "mypy>=1.0.0",
84
+ "pre-commit>=3.0.0",
85
+ "httpx>=0.24.0",
86
+ "build>=0.10.0",
87
+ ]
88
+
89
+ [tool.setuptools.packages.find]
90
+ where = ["src"]
91
+ include = ["pyintake*"]
92
+
93
+ [tool.setuptools.package-data]
94
+ pyintake = [
95
+ "py.typed",
96
+ "data/seed/*.yaml",
97
+ ]
98
+
99
+ [project.scripts]
100
+ pyintake = "pyintake.cli:main"
101
+
102
+ # =============================================================================
103
+ # Tool Configuration
104
+ # =============================================================================
105
+
106
+ [tool.ruff]
107
+ target-version = "py311"
108
+ line-length = 88
109
+ src = ["src", "tests"]
110
+
111
+ [tool.ruff.lint]
112
+ select = ["E", "W", "F", "I", "B", "C4", "UP"]
113
+ ignore = [
114
+ "E501",
115
+ "B008",
116
+ "B904",
117
+ "B028",
118
+ "B007",
119
+ "B017",
120
+ "E402",
121
+ "E731",
122
+ "B027",
123
+ "B905",
124
+ ]
125
+
126
+ [tool.ruff.lint.per-file-ignores]
127
+ "__init__.py" = ["F401"]
128
+ "tests/**" = ["B904", "B017", "E402"]
129
+
130
+ [tool.ruff.lint.isort]
131
+ known-first-party = ["pyintake"]
132
+
133
+ [tool.ruff.format]
134
+ quote-style = "double"
135
+
136
+ [tool.mypy]
137
+ python_version = "3.11"
138
+ warn_return_any = true
139
+ warn_unused_configs = true
140
+ disallow_untyped_defs = false
141
+ ignore_missing_imports = true
142
+
143
+ [tool.pytest.ini_options]
144
+ minversion = "7.0"
145
+ testpaths = ["tests"]
146
+ python_files = ["test_*.py"]
147
+ python_classes = ["Test*"]
148
+ python_functions = ["test_*"]
149
+ asyncio_mode = "auto"
150
+ asyncio_default_fixture_loop_scope = "function"
151
+ addopts = [
152
+ "--strict-markers",
153
+ "--tb=short",
154
+ ]
155
+ markers = [
156
+ "slow: marks tests as slow (deselect with '-m \"not slow\"')",
157
+ "integration: marks tests as integration tests",
158
+ "unit: marks tests as unit tests",
159
+ "asyncio: mark test as async (pytest-asyncio)",
160
+ ]
161
+ filterwarnings = [
162
+ "error",
163
+ "ignore::UserWarning",
164
+ ]
165
+
166
+ [tool.coverage.run]
167
+ source = ["pyintake"]
168
+
169
+ [tool.coverage.report]
170
+ show_missing = true
171
+ skip_empty = true
172
+ exclude_lines = [
173
+ "pragma: no cover",
174
+ "if TYPE_CHECKING:",
175
+ "if __name__ == .__main__.",
176
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,83 @@
1
+ """PyIntake — Transport-agnostic data ingestion system.
2
+
3
+ Collects, validates, deduplicates, and buffers events from any transport,
4
+ then drains them into pluggable downstream consumers with priority ordering
5
+ and backpressure signaling.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pyintake._protocols import Consumer, MetricsCollector, TransportAdapter, Validator
11
+ from pyintake._types import (
12
+ BackpressureSignal,
13
+ BufferEntry,
14
+ ConsumeResult,
15
+ IngestResult,
16
+ Priority,
17
+ ValidationOutcome,
18
+ )
19
+ from pyintake.buffer._dedup import SlidingWindowDedup
20
+ from pyintake.buffer._priority_buffer import PriorityBuffer
21
+ from pyintake.core._drain import AdaptiveDrainLoop
22
+ from pyintake.core._engine import IngestionEngine
23
+ from pyintake.core._rate_limiter import TokenBucketRateLimiter
24
+ from pyintake.errors import (
25
+ AdapterError,
26
+ BufferFullError,
27
+ ConfigError,
28
+ ConsumerError,
29
+ DrainError,
30
+ DuplicateEventError,
31
+ ErrorCode,
32
+ PyIntakeError,
33
+ RateLimitedError,
34
+ ShutdownTimeoutError,
35
+ ValidationError,
36
+ )
37
+ from pyintake.event import IngestEvent
38
+ from pyintake.metrics._collector import InMemoryMetricsCollector
39
+ from pyintake.metrics._models import MetricsSnapshot, SourceMetrics
40
+ from pyintake.validation._chain import ValidationChain
41
+
42
+ __all__ = [
43
+ # Core event type
44
+ "IngestEvent",
45
+ # Value types and enums
46
+ "Priority",
47
+ "BackpressureSignal",
48
+ "IngestResult",
49
+ "ValidationOutcome",
50
+ "ConsumeResult",
51
+ "BufferEntry",
52
+ # Protocols
53
+ "TransportAdapter",
54
+ "Validator",
55
+ "Consumer",
56
+ "MetricsCollector",
57
+ # Engine and drain
58
+ "IngestionEngine",
59
+ "AdaptiveDrainLoop",
60
+ # Buffer
61
+ "PriorityBuffer",
62
+ "SlidingWindowDedup",
63
+ # Validation
64
+ "ValidationChain",
65
+ # Rate limiting
66
+ "TokenBucketRateLimiter",
67
+ # Metrics
68
+ "InMemoryMetricsCollector",
69
+ "MetricsSnapshot",
70
+ "SourceMetrics",
71
+ # Errors
72
+ "PyIntakeError",
73
+ "ErrorCode",
74
+ "BufferFullError",
75
+ "ValidationError",
76
+ "DuplicateEventError",
77
+ "RateLimitedError",
78
+ "AdapterError",
79
+ "DrainError",
80
+ "ConfigError",
81
+ "ConsumerError",
82
+ "ShutdownTimeoutError",
83
+ ]
@@ -0,0 +1,145 @@
1
+ """Protocol definitions for PyIntake pluggable interfaces.
2
+
3
+ Protocols define the contracts for transport adapters, validators,
4
+ consumers, and metrics collectors. Implementations can be swapped
5
+ in via dependency injection without coupling to concrete classes.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
11
+
12
+ if TYPE_CHECKING:
13
+ from pyintake._types import ConsumeResult, ValidationOutcome
14
+ from pyintake.event import IngestEvent
15
+ from pyintake.metrics._models import MetricsSnapshot
16
+
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Transport adapter protocol
20
+ # ---------------------------------------------------------------------------
21
+
22
+
23
+ @runtime_checkable
24
+ class TransportAdapter(Protocol):
25
+ """Receives events from a transport and feeds them into IngestionEngine.
26
+
27
+ Implementations handle transport-specific concerns (Kafka consumer,
28
+ HTTP server, file watcher, etc.) and call engine.ingest() for each event.
29
+ """
30
+
31
+ async def start(self, engine: Any) -> None:
32
+ """Start the adapter and begin feeding events to the engine.
33
+
34
+ Args:
35
+ engine: The IngestionEngine to feed events into.
36
+ """
37
+ ...
38
+
39
+ async def stop(self) -> None:
40
+ """Stop the adapter and release transport resources."""
41
+ ...
42
+
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # Validator protocol
46
+ # ---------------------------------------------------------------------------
47
+
48
+
49
+ @runtime_checkable
50
+ class Validator(Protocol):
51
+ """Validates an IngestEvent before it enters the buffer.
52
+
53
+ Validators run in the ingestion path (before buffering), so they
54
+ must be fast and synchronous. Heavy validation should be done
55
+ downstream by consumers.
56
+ """
57
+
58
+ @property
59
+ def name(self) -> str:
60
+ """Human-readable name for metrics and logging."""
61
+ ...
62
+
63
+ def validate(self, event: IngestEvent) -> ValidationOutcome:
64
+ """Validate a single event.
65
+
66
+ Args:
67
+ event: The event to validate.
68
+
69
+ Returns:
70
+ Outcome indicating whether the event is valid.
71
+ """
72
+ ...
73
+
74
+
75
+ # ---------------------------------------------------------------------------
76
+ # Consumer protocol
77
+ # ---------------------------------------------------------------------------
78
+
79
+
80
+ @runtime_checkable
81
+ class Consumer(Protocol):
82
+ """Downstream consumer that receives drained event batches.
83
+
84
+ Consumers are the output side of the ingestion pipeline. They
85
+ receive batches of validated, deduplicated events from the
86
+ drain loop. Examples: pystator orchestrator, pycharter validator,
87
+ file collector for offline analysis.
88
+ """
89
+
90
+ async def consume(self, batch: list[IngestEvent]) -> ConsumeResult:
91
+ """Process a batch of events.
92
+
93
+ Args:
94
+ batch: List of events drained from the buffer.
95
+
96
+ Returns:
97
+ Result indicating success/failure and events processed.
98
+ """
99
+ ...
100
+
101
+ async def close(self) -> None:
102
+ """Release any resources held by the consumer."""
103
+ ...
104
+
105
+
106
+ # ---------------------------------------------------------------------------
107
+ # Metrics collector protocol
108
+ # ---------------------------------------------------------------------------
109
+
110
+
111
+ @runtime_checkable
112
+ class MetricsCollector(Protocol):
113
+ """Collects ingestion metrics programmatically.
114
+
115
+ All methods are synchronous and must be fast — they are called
116
+ in the hot ingestion path.
117
+ """
118
+
119
+ def record_accepted(self, event: IngestEvent) -> None:
120
+ """Record a successfully accepted event."""
121
+ ...
122
+
123
+ def record_rejected(self, event: IngestEvent, reason: str) -> None:
124
+ """Record a rejected event with the rejection reason."""
125
+ ...
126
+
127
+ def record_drained(self, count: int, sources: list[str] | None = None) -> None:
128
+ """Record that count events were drained to consumers."""
129
+ ...
130
+
131
+ def record_deduplicated(self, event: IngestEvent) -> None:
132
+ """Record a deduplicated (duplicate-rejected) event."""
133
+ ...
134
+
135
+ def record_shed(self, event: IngestEvent) -> None:
136
+ """Record an event shed due to buffer pressure."""
137
+ ...
138
+
139
+ def record_dead_letter(self, event: IngestEvent) -> None:
140
+ """Record an event that exceeded max retries."""
141
+ ...
142
+
143
+ def snapshot(self) -> MetricsSnapshot:
144
+ """Return a frozen point-in-time snapshot of all metrics."""
145
+ ...