airflow-postgres-csv 0.2.0__tar.gz → 0.3.0__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.
@@ -46,6 +46,9 @@ jobs:
46
46
  verify:
47
47
  needs: publish
48
48
  runs-on: ubuntu-latest
49
+ strategy:
50
+ matrix:
51
+ airflow: ["2", "3"]
49
52
  steps:
50
53
  - uses: actions/checkout@v4
51
54
 
@@ -54,13 +57,11 @@ jobs:
54
57
  with:
55
58
  python-version: "3.12"
56
59
 
60
+ - name: Install tox
61
+ run: pip install tox
62
+
57
63
  - name: Wait for PyPI to update
58
64
  run: sleep 30
59
65
 
60
- - name: Install from PyPI and test dependencies
61
- run: |
62
- pip install airflow-postgres-csv pytest
63
- pip show airflow-postgres-csv
64
-
65
- - name: Run tests against installed package
66
- run: pytest tests/ -v
66
+ - name: Run tests against published package (Airflow ${{ matrix.airflow }})
67
+ run: tox -e verify-airflow${{ matrix.airflow }}
@@ -0,0 +1,42 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python-version: ["3.10", "3.11", "3.12"]
16
+ tox-env: ["airflow2", "airflow3"]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v4
23
+
24
+ - name: Set up Python ${{ matrix.python-version }}
25
+ run: uv python install ${{ matrix.python-version }}
26
+
27
+ - name: Install tox
28
+ run: |
29
+ uv venv .venv --python ${{ matrix.python-version }}
30
+ uv pip install tox tox-uv --python .venv/bin/python
31
+
32
+ - name: Run ${{ matrix.tox-env }} tests
33
+ run: .venv/bin/tox -e ${{ matrix.tox-env }}
34
+
35
+ - name: Upload coverage to Codecov
36
+ if: matrix.python-version == '3.12' && matrix.tox-env == 'airflow3'
37
+ uses: codecov/codecov-action@v4
38
+ with:
39
+ files: coverage.xml
40
+ fail_ci_if_error: false
41
+ env:
42
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airflow-postgres-csv
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Airflow operators for PostgreSQL <-> CSV file transfers using COPY
5
5
  Project-URL: Repository, https://github.com/Redevil10/airflow-postgres-csv
6
6
  Author: Qing Wan
@@ -10,18 +10,26 @@ Classifier: Framework :: Apache Airflow
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: >=3.10
13
- Requires-Dist: apache-airflow-providers-postgres>=6.0.0
14
- Requires-Dist: apache-airflow>=3.0.0
13
+ Requires-Dist: apache-airflow-providers-postgres>=5.0
14
+ Requires-Dist: apache-airflow>=2.9
15
15
  Provides-Extra: dev
16
16
  Requires-Dist: pre-commit; extra == 'dev'
17
17
  Requires-Dist: pytest-cov; extra == 'dev'
18
18
  Requires-Dist: pytest>=8.0; extra == 'dev'
19
19
  Requires-Dist: ruff; extra == 'dev'
20
+ Requires-Dist: tox>=4.0; extra == 'dev'
20
21
  Description-Content-Type: text/markdown
21
22
 
22
23
  # airflow-postgres-csv
23
24
 
24
- Airflow 3 operators for bulk PostgreSQL <-> CSV transfers using `COPY`.
25
+ [![lint](https://github.com/Redevil10/airflow-postgres-csv/actions/workflows/lint.yml/badge.svg)](https://github.com/Redevil10/airflow-postgres-csv/actions/workflows/lint.yml)
26
+ [![tests](https://github.com/Redevil10/airflow-postgres-csv/actions/workflows/test.yml/badge.svg)](https://github.com/Redevil10/airflow-postgres-csv/actions/workflows/test.yml)
27
+ [![codecov](https://codecov.io/github/Redevil10/airflow-postgres-csv/graph/badge.svg)](https://codecov.io/gh/Redevil10/airflow-postgres-csv)
28
+ [![python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue.svg)](https://www.python.org/downloads/)
29
+ [![airflow](https://img.shields.io/badge/airflow-2.9%2B%20%7C%203.x-blue.svg)](https://airflow.apache.org/)
30
+ [![PyPI](https://img.shields.io/pypi/v/airflow-postgres-csv)](https://pypi.org/project/airflow-postgres-csv/)
31
+
32
+ Airflow operators for bulk PostgreSQL <-> CSV transfers using `COPY`. Supports Airflow 2.9+ and Airflow 3.
25
33
 
26
34
  ## Operators
27
35
 
@@ -126,10 +134,29 @@ CsvToPostgresOperator(
126
134
  | `null_string` | String representing NULL | `""` |
127
135
  | `timeout` | Query timeout in minutes | `60` |
128
136
 
137
+ ## Development
138
+
139
+ ### Running tests
140
+
141
+ Tests can be run against both supported Airflow versions using [tox](https://tox.wiki):
142
+
143
+ ```bash
144
+ pip install tox
145
+
146
+ tox -e airflow2 # test against Airflow 2.x
147
+ tox -e airflow3 # test against Airflow 3.x
148
+ tox # run both
149
+ ```
150
+
151
+ Each environment installs the correct Airflow and provider versions automatically — no manual dependency management needed.
152
+
129
153
  ## Requirements
130
154
 
131
- - Apache Airflow >= 3.0.0
132
- - apache-airflow-providers-postgres >= 6.0.0
155
+ | | Airflow 2 | Airflow 3 |
156
+ |---|---|---|
157
+ | `apache-airflow` | `>=2.9, <3.0` | `>=3.0` |
158
+ | `apache-airflow-providers-postgres` | `>=5.0, <6.0` | `>=6.0` |
159
+ | Python | 3.10 – 3.12 | 3.10 – 3.12 |
133
160
 
134
161
  ## License
135
162
 
@@ -1,6 +1,13 @@
1
1
  # airflow-postgres-csv
2
2
 
3
- Airflow 3 operators for bulk PostgreSQL <-> CSV transfers using `COPY`.
3
+ [![lint](https://github.com/Redevil10/airflow-postgres-csv/actions/workflows/lint.yml/badge.svg)](https://github.com/Redevil10/airflow-postgres-csv/actions/workflows/lint.yml)
4
+ [![tests](https://github.com/Redevil10/airflow-postgres-csv/actions/workflows/test.yml/badge.svg)](https://github.com/Redevil10/airflow-postgres-csv/actions/workflows/test.yml)
5
+ [![codecov](https://codecov.io/github/Redevil10/airflow-postgres-csv/graph/badge.svg)](https://codecov.io/gh/Redevil10/airflow-postgres-csv)
6
+ [![python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue.svg)](https://www.python.org/downloads/)
7
+ [![airflow](https://img.shields.io/badge/airflow-2.9%2B%20%7C%203.x-blue.svg)](https://airflow.apache.org/)
8
+ [![PyPI](https://img.shields.io/pypi/v/airflow-postgres-csv)](https://pypi.org/project/airflow-postgres-csv/)
9
+
10
+ Airflow operators for bulk PostgreSQL <-> CSV transfers using `COPY`. Supports Airflow 2.9+ and Airflow 3.
4
11
 
5
12
  ## Operators
6
13
 
@@ -105,10 +112,29 @@ CsvToPostgresOperator(
105
112
  | `null_string` | String representing NULL | `""` |
106
113
  | `timeout` | Query timeout in minutes | `60` |
107
114
 
115
+ ## Development
116
+
117
+ ### Running tests
118
+
119
+ Tests can be run against both supported Airflow versions using [tox](https://tox.wiki):
120
+
121
+ ```bash
122
+ pip install tox
123
+
124
+ tox -e airflow2 # test against Airflow 2.x
125
+ tox -e airflow3 # test against Airflow 3.x
126
+ tox # run both
127
+ ```
128
+
129
+ Each environment installs the correct Airflow and provider versions automatically — no manual dependency management needed.
130
+
108
131
  ## Requirements
109
132
 
110
- - Apache Airflow >= 3.0.0
111
- - apache-airflow-providers-postgres >= 6.0.0
133
+ | | Airflow 2 | Airflow 3 |
134
+ |---|---|---|
135
+ | `apache-airflow` | `>=2.9, <3.0` | `>=3.0` |
136
+ | `apache-airflow-providers-postgres` | `>=5.0, <6.0` | `>=6.0` |
137
+ | Python | 3.10 – 3.12 | 3.10 – 3.12 |
112
138
 
113
139
  ## License
114
140
 
@@ -0,0 +1,96 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "airflow-postgres-csv"
7
+ version = "0.3.0"
8
+ description = "Airflow operators for PostgreSQL <-> CSV file transfers using COPY"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Qing Wan" }]
13
+ classifiers = [
14
+ "Framework :: Apache Airflow",
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ ]
18
+
19
+ dependencies = [
20
+ "apache-airflow>=2.9",
21
+ "apache-airflow-providers-postgres>=5.0",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ dev = [
26
+ "pytest>=8.0",
27
+ "pytest-cov",
28
+ "ruff",
29
+ "pre-commit",
30
+ "tox>=4.0",
31
+ ]
32
+
33
+ [project.urls]
34
+ Repository = "https://github.com/Redevil10/airflow-postgres-csv"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/airflow_postgres_csv"]
38
+
39
+ [tool.ruff]
40
+ target-version = "py310"
41
+ line-length = 99
42
+
43
+ [tool.ruff.lint]
44
+ select = ["E", "F", "I", "UP"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+
49
+ [tool.tox]
50
+ env_list = ["airflow2", "airflow3"]
51
+
52
+ [tool.tox.env.airflow2]
53
+ description = "Test against Apache Airflow 2.x (requires Python 3.10–3.12)"
54
+ skip_install = true
55
+ set_env = "PYTHONPATH = {toxinidir}/src"
56
+ deps = [
57
+ "apache-airflow>=2.9,<3.0",
58
+ "apache-airflow-providers-postgres>=5.0,<6.0",
59
+ "pytest>=8.0",
60
+ "pytest-cov",
61
+ ]
62
+ commands = [["pytest", "--cov=airflow_postgres_csv", "--cov-report=xml", "{posargs:tests/}"]]
63
+
64
+ [tool.tox.env.airflow3]
65
+ description = "Test against Apache Airflow 3.x"
66
+ skip_install = true
67
+ set_env = "PYTHONPATH = {toxinidir}/src"
68
+ deps = [
69
+ "apache-airflow>=3.0",
70
+ "apache-airflow-providers-postgres>=6.0",
71
+ "pytest>=8.0",
72
+ "pytest-cov",
73
+ ]
74
+ commands = [["pytest", "--cov=airflow_postgres_csv", "--cov-report=xml", "{posargs:tests/}"]]
75
+
76
+ [tool.tox.env.verify-airflow2]
77
+ description = "Verify the published PyPI package works with Airflow 2.x"
78
+ skip_install = true
79
+ deps = [
80
+ "airflow-postgres-csv",
81
+ "apache-airflow>=2.9,<3.0",
82
+ "apache-airflow-providers-postgres>=5.0,<6.0",
83
+ "pytest>=8.0",
84
+ ]
85
+ commands = [["pytest", "{posargs:tests/}"]]
86
+
87
+ [tool.tox.env.verify-airflow3]
88
+ description = "Verify the published PyPI package works with Airflow 3.x"
89
+ skip_install = true
90
+ deps = [
91
+ "airflow-postgres-csv",
92
+ "apache-airflow>=3.0",
93
+ "apache-airflow-providers-postgres>=6.0",
94
+ "pytest>=8.0",
95
+ ]
96
+ commands = [["pytest", "{posargs:tests/}"]]
@@ -6,7 +6,11 @@ from collections.abc import Sequence
6
6
 
7
7
  from airflow.exceptions import AirflowException
8
8
  from airflow.providers.postgres.hooks.postgres import PostgresHook
9
- from airflow.sdk.bases.operator import BaseOperator
9
+
10
+ try:
11
+ from airflow.sdk.bases.operator import BaseOperator # Airflow 3
12
+ except ImportError:
13
+ from airflow.models import BaseOperator # Airflow 2
10
14
 
11
15
 
12
16
  class PostgresToCsvOperator(BaseOperator):
@@ -194,3 +194,81 @@ class TestCsvToPostgresOperator:
194
194
  result = op.execute(context={})
195
195
  assert result == 42 # mocked rowcount
196
196
  mock_pg_hook["cursor"].copy_expert.assert_called_once()
197
+
198
+
199
+ # ---------------------------------------------------------------------------
200
+ # Airflow 2 compatibility tests
201
+ #
202
+ # In Airflow 3, airflow.models.BaseOperator is a re-export of
203
+ # airflow.sdk.bases.operator.BaseOperator, so these tests run in either version.
204
+ # The try/except in operators.py is what enables the code to import on a real
205
+ # Airflow 2 install where airflow.sdk does not exist.
206
+ # ---------------------------------------------------------------------------
207
+
208
+
209
+ class TestAirflow2Compatibility:
210
+ """
211
+ Verify that operators are compatible with the Airflow 2 import path
212
+ (``airflow.models.BaseOperator``).
213
+
214
+ A real Airflow 2 environment cannot be simulated in an Airflow 3 process
215
+ because Airflow 3 re-exports ``BaseOperator`` through the same ``airflow.sdk``
216
+ machinery from both import paths. These tests confirm that:
217
+
218
+ * ``from airflow.models import BaseOperator`` resolves successfully (the
219
+ fallback import the operators use on Airflow 2).
220
+ * Both operators are subclasses of that ``BaseOperator``.
221
+ * Both operators execute correctly when invoked the Airflow 2 way.
222
+ """
223
+
224
+ def test_fallback_import_target_is_accessible(self):
225
+ """airflow.models.BaseOperator can be imported (the Airflow 2 fallback path)."""
226
+ from airflow.models import BaseOperator
227
+
228
+ assert BaseOperator is not None
229
+
230
+ def test_operators_subclass_models_baseoperator(self):
231
+ """Both operators are subclasses of airflow.models.BaseOperator."""
232
+ from airflow.models import BaseOperator
233
+
234
+ from airflow_postgres_csv.operators import CsvToPostgresOperator, PostgresToCsvOperator
235
+
236
+ assert issubclass(PostgresToCsvOperator, BaseOperator)
237
+ assert issubclass(CsvToPostgresOperator, BaseOperator)
238
+
239
+ def test_postgres_to_csv_is_instance_of_models_baseoperator(self, mock_pg_hook, tmp_path):
240
+ """A PostgresToCsvOperator instance is recognised as a BaseOperator on Airflow 2."""
241
+ from airflow.models import BaseOperator
242
+
243
+ from airflow_postgres_csv.operators import PostgresToCsvOperator
244
+
245
+ csv_path = str(tmp_path / "out.csv")
246
+ op = PostgresToCsvOperator(
247
+ task_id="test_af2",
248
+ conn_id="test_conn",
249
+ csv_file_path=csv_path,
250
+ sql="SELECT 1",
251
+ )
252
+ assert isinstance(op, BaseOperator)
253
+ result = op.execute(context={})
254
+ assert result == csv_path
255
+ mock_pg_hook["cursor"].copy_expert.assert_called_once()
256
+
257
+ def test_csv_to_postgres_is_instance_of_models_baseoperator(self, mock_pg_hook, tmp_path):
258
+ """A CsvToPostgresOperator instance is recognised as a BaseOperator on Airflow 2."""
259
+ from airflow.models import BaseOperator
260
+
261
+ from airflow_postgres_csv.operators import CsvToPostgresOperator
262
+
263
+ csv_file = tmp_path / "data.csv"
264
+ csv_file.write_text("a,b\n1,2\n")
265
+ op = CsvToPostgresOperator(
266
+ task_id="test_af2",
267
+ conn_id="test_conn",
268
+ table_name="my_table",
269
+ csv_file_path=str(csv_file),
270
+ )
271
+ assert isinstance(op, BaseOperator)
272
+ result = op.execute(context={})
273
+ assert result == 42 # mocked rowcount
274
+ mock_pg_hook["cursor"].copy_expert.assert_called_once()
@@ -1,30 +0,0 @@
1
- name: Tests
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
- branches: [main]
8
-
9
- jobs:
10
- test:
11
- runs-on: ubuntu-latest
12
- strategy:
13
- matrix:
14
- python-version: ["3.10", "3.11", "3.12"]
15
- steps:
16
- - uses: actions/checkout@v4
17
-
18
- - name: Install uv
19
- uses: astral-sh/setup-uv@v4
20
-
21
- - name: Set up Python ${{ matrix.python-version }}
22
- run: uv python install ${{ matrix.python-version }}
23
-
24
- - name: Install dependencies
25
- run: |
26
- uv venv .venv --python ${{ matrix.python-version }}
27
- uv pip install -e ".[dev]" --python .venv/bin/python
28
-
29
- - name: Test
30
- run: .venv/bin/pytest -v
@@ -1,46 +0,0 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "airflow-postgres-csv"
7
- version = "0.2.0"
8
- description = "Airflow operators for PostgreSQL <-> CSV file transfers using COPY"
9
- readme = "README.md"
10
- license = "MIT"
11
- requires-python = ">=3.10"
12
- authors = [{ name = "Qing Wan" }]
13
- classifiers = [
14
- "Framework :: Apache Airflow",
15
- "Programming Language :: Python :: 3",
16
- "License :: OSI Approved :: MIT License",
17
- ]
18
-
19
- dependencies = [
20
- "apache-airflow>=3.0.0",
21
- "apache-airflow-providers-postgres>=6.0.0",
22
- ]
23
-
24
- [project.optional-dependencies]
25
- dev = [
26
- "pytest>=8.0",
27
- "pytest-cov",
28
- "ruff",
29
- "pre-commit",
30
- ]
31
-
32
- [project.urls]
33
- Repository = "https://github.com/Redevil10/airflow-postgres-csv"
34
-
35
- [tool.hatch.build.targets.wheel]
36
- packages = ["src/airflow_postgres_csv"]
37
-
38
- [tool.ruff]
39
- target-version = "py310"
40
- line-length = 99
41
-
42
- [tool.ruff.lint]
43
- select = ["E", "F", "I", "UP"]
44
-
45
- [tool.pytest.ini_options]
46
- testpaths = ["tests"]