From d643538e9cb1ebfb22a26b8526d6245dbad5dc81 Mon Sep 17 00:00:00 2001 From: Pierre Camilleri Date: Mon, 27 Jul 2026 17:47:10 +0200 Subject: [PATCH 01/11] =?UTF-8?q?=F0=9F=94=B5=20improve=20Header=20doc,=20?= =?UTF-8?q?deprecate=20usage=20as=20List?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frictionless/table/header.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/frictionless/table/header.py b/frictionless/table/header.py index ce7907b025..9e59ce4dca 100644 --- a/frictionless/table/header.py +++ b/frictionless/table/header.py @@ -18,12 +18,20 @@ class Header(List[str]): # type: ignore """Header representation + Compares the header row read from the data source (the "labels") with the + fields declared in the schema, and reports the mismatches as errors. + > Constructor of this object is not Public API + > Deprecated: using a `Header` directly as a list is deprecated, as the list + > ambiguously holds the names of the *schema* fieldss. Use its properties instead: + > `labels` for the header row as read from the data source, `fields`/`field_names` + > for the schema fields. + Parameters: - labels (any[]): header row labels - fields (Field[]): table fields - row_numbers (int[]): row numbers + labels (any[]): the header row as read from the data source + fields (Field[]): the fields declared in the schema, in schema order + row_numbers (int[]): row numbers the header spans in the data source ignore_case (bool): ignore case fields_match (str): how the fields match the data source @@ -60,7 +68,7 @@ def __init__( def labels(self): """ Returns: - Schema: table labels + str[]: the header row as read from the data source """ return self.__labels @@ -68,7 +76,7 @@ def labels(self): def fields(self): """ Returns: - Schema: table fields + Field[]: copies of the schema fields, in schema order """ return self.__fields @@ -76,7 +84,7 @@ def fields(self): def field_names(self): """ Returns: - str[]: table field names + str[]: the names of the schema fields, in schema order """ return self.__field_names From 01be157d70a56daea45ec931fdd8865ed6bb3685 Mon Sep 17 00:00:00 2001 From: Pierre Camilleri Date: Mon, 27 Jul 2026 17:59:00 +0200 Subject: [PATCH 02/11] Rm internal usage of Header as list --- .../formats/csv/__spec__/test_parser.py | 50 +++++++++---------- .../excel/parsers/__spec__/test_xls.py | 18 +++---- .../excel/parsers/__spec__/test_xlsx.py | 18 +++---- .../formats/gsheets/__spec__/test_parser.py | 6 +-- .../formats/html/__spec__/test_parser.py | 6 +-- .../formats/inline/__spec__/test_parser.py | 14 +++--- .../json/parsers/__spec__/test_json.py | 14 +++--- .../json/parsers/__spec__/test_jsonl.py | 8 +-- .../formats/ods/__spec__/test_parser.py | 20 ++++---- .../formats/pandas/__spec__/test_parser.py | 2 +- .../formats/parquet/__spec__/test_parser.py | 10 ++-- .../formats/spss/__spec__/test_parser.py | 4 +- .../sql/__spec__/duckdb/test_parser.py | 16 +++--- .../formats/sql/__spec__/mysql/test_parser.py | 4 +- .../sql/__spec__/postgresql/test_parser.py | 4 +- .../formats/sql/__spec__/test_parser.py | 16 +++--- .../formats/yaml/__spec__/test_parser.py | 8 +-- .../package/__spec__/test_resources.py | 2 +- .../portals/ckan/__spec__/test_adapter.py | 2 +- .../resource/__spec__/test_validate.py | 2 +- .../__spec__/test_validate_dialect.py | 12 ++--- frictionless/resource/resource.py | 2 +- .../__spec__/table/test_compression.py | 28 +++++------ .../resources/__spec__/table/test_detector.py | 22 ++++---- .../resources/__spec__/table/test_dialect.py | 38 +++++++------- .../resources/__spec__/table/test_encoding.py | 6 +-- .../resources/__spec__/table/test_general.py | 11 ++-- .../__spec__/table/test_innerpath.py | 6 +-- .../resources/__spec__/table/test_open.py | 20 ++++---- .../resources/__spec__/table/test_schema.py | 4 +- .../resources/__spec__/table/test_write.py | 4 +- frictionless/resources/table.py | 2 +- .../schemes/aws/loaders/__spec__/test_s3.py | 6 +-- .../schemes/buffer/__spec__/test_loader.py | 2 +- .../schemes/local/__spec__/test_loader.py | 4 +- .../schemes/multipart/__spec__/test_loader.py | 4 +- .../schemes/remote/__spec__/test_loader.py | 4 +- .../schemes/stream/__spec__/test_loader.py | 4 +- frictionless/steps/table/table_normalize.py | 2 +- frictionless/system/__spec__/test_system.py | 2 +- frictionless/table/__spec__/test_header.py | 21 ++++++-- frictionless/table/header.py | 9 ++-- 42 files changed, 227 insertions(+), 210 deletions(-) diff --git a/frictionless/formats/csv/__spec__/test_parser.py b/frictionless/formats/csv/__spec__/test_parser.py index 3a17427091..6e00091dc2 100644 --- a/frictionless/formats/csv/__spec__/test_parser.py +++ b/frictionless/formats/csv/__spec__/test_parser.py @@ -13,7 +13,7 @@ def test_csv_parser(): with TableResource(path="data/table.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -22,7 +22,7 @@ def test_csv_parser(): def test_csv_parser_with_bom(): with TableResource(path="data/bom.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -31,7 +31,7 @@ def test_csv_parser_with_bom(): def test_csv_parser_with_bom_with_encoding(): with TableResource(path="data/bom.csv", encoding="utf-8") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -41,7 +41,7 @@ def test_csv_parser_with_bom_with_encoding(): def test_csv_parser_excel(): data = b"header1,header2\nvalue1,value2\nvalue3,value4" with TableResource(data=data, format="csv") as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, @@ -52,7 +52,7 @@ def test_csv_parser_excel_tab(): data = b"header1\theader2\nvalue1\tvalue2\nvalue3\tvalue4" control = formats.CsvControl(delimiter="\t") with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, @@ -62,7 +62,7 @@ def test_csv_parser_excel_tab(): def test_csv_parser_unix(): data = b'"header1","header2"\n"value1","value2"\n"value3","value4"' with TableResource(data=data, format="csv") as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, @@ -72,7 +72,7 @@ def test_csv_parser_unix(): def test_csv_parser_escaping(): control = formats.CsvControl(escape_char="\\") with TableResource(path="data/escaping.csv", control=control) as resource: - assert resource.header == ["ID", "Test"] + assert resource.header.field_names == ["ID", "Test"] assert resource.read_rows() == [ {"ID": 1, "Test": "Test line 1"}, {"ID": 2, "Test": 'Test " line 2'}, @@ -82,7 +82,7 @@ def test_csv_parser_escaping(): def test_csv_parser_doublequote(): with TableResource(path="data/doublequote.csv") as resource: - assert len(resource.header) == 17 + assert len(resource.header.field_names) == 17 for row in resource.row_stream: assert len(row) == 17 @@ -90,7 +90,7 @@ def test_csv_parser_doublequote(): def test_csv_parser_stream(): data = open("data/table.csv", mode="rb") with TableResource(data=data, format="csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -100,7 +100,7 @@ def test_csv_parser_stream(): def test_csv_parser_buffer(): data = "id,name\n1,english\n2,中国人\n".encode("utf-8") with TableResource(data=data, format="csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -111,7 +111,7 @@ def test_csv_parser_buffer(): @pytest.mark.skipif(sys.version_info < (3, 10), reason="pytest-vcr bug in Python3.8/9") def test_csv_parser_remote(): with TableResource(path=BASEURL % "data/table.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -122,7 +122,7 @@ def test_csv_parser_remote(): def test_csv_parser_remote_non_ascii_url(): path = "http://data.defra.gov.uk/ops/government_procurement_card/over_£500_GPC_apr_2013.csv" with TableResource(path=path) as resource: - assert resource.header == [ + assert resource.header.field_names == [ "Entity", "Transaction Posting Date", "Merchant Name", @@ -135,7 +135,7 @@ def test_csv_parser_delimiter(): data = b'"header1";"header2"\n"value1";"value2"\n"value3";"value4"' control = formats.CsvControl(delimiter=";") with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, @@ -146,7 +146,7 @@ def test_csv_parser_escapechar(): data = b"header1%,header2\nvalue1%,value2\nvalue3%,value4" control = formats.CsvControl(escape_char="%") with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header == ["header1,header2"] + assert resource.header.field_names == ["header1,header2"] assert resource.read_rows() == [ {"header1,header2": "value1,value2"}, {"header1,header2": "value3,value4"}, @@ -157,7 +157,7 @@ def test_csv_parser_quotechar(): data = b"%header1,header2%\n%value1,value2%\n%value3,value4%" control = formats.CsvControl(quote_char="%") with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header == ["header1,header2"] + assert resource.header.field_names == ["header1,header2"] assert resource.read_rows() == [ {"header1,header2": "value1,value2"}, {"header1,header2": "value3,value4"}, @@ -168,7 +168,7 @@ def test_csv_parser_skipinitialspace(): data = b"header1, header2\nvalue1, value2\nvalue3, value4" control = formats.CsvControl(skip_initial_space=False) with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": " value2"}, {"header1": "value3", "header2": " value4"}, @@ -178,7 +178,7 @@ def test_csv_parser_skipinitialspace(): def test_csv_parser_skipinitialspace_default(): data = b"header1, header2\nvalue1, value2\nvalue3, value4" with TableResource(data=data, format="csv") as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, @@ -220,9 +220,9 @@ def test_csv_parser_dialect_should_not_persist_if_sniffing_fails_issue_goodtable data2 = b"a,b,c\n#comment" control = formats.CsvControl(delimiter=";") with TableResource(data=data1, format="csv", control=control) as resource: - assert resource.header == ["a", "b", "c"] + assert resource.header.field_names == ["a", "b", "c"] with TableResource(data=data2, format="csv") as resource: - assert resource.header == ["a", "b", "c"] + assert resource.header.field_names == ["a", "b", "c"] # TODO: recover on py3.11+ @@ -231,7 +231,7 @@ def test_csv_parser_quotechar_is_empty_string(): data = b'header1,header2",header3\nvalue1,value2",value3' control = formats.CsvControl(quote_char="") with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header == ["header1", 'header2"', "header3"] + assert resource.header.field_names == ["header1", 'header2"', "header3"] assert resource.read_rows() == [ {"header1": "value1", 'header2"': 'value2"', "header3": "value3"}, ] @@ -241,7 +241,7 @@ def test_csv_parser_format_tsv(): detector = Detector(schema_patch={"missingValues": ["\\N"]}) with TableResource(path="data/table.tsv", detector=detector) as resource: assert resource.dialect.to_descriptor() == {"csv": {"delimiter": "\t"}} - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -258,7 +258,7 @@ def test_csv_parser_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.csv"))) source.write(target) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -272,7 +272,7 @@ def test_csv_parser_write_delimiter(tmpdir): target = TableResource(path=str(tmpdir.join("table.csv")), control=control) source.write(target) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.dialect.to_descriptor() == {"csv": {"delimiter": ";"}} assert target.read_rows() == [ {"id": 1, "name": "english"}, @@ -286,7 +286,7 @@ def test_csv_parser_write_inline_source(tmpdir): target = TableResource(path=str(tmpdir.join("table.csv"))) source.write(target) with target: - assert target.header == ["key1", "key2"] + assert target.header.field_names == ["key1", "key2"] assert target.read_rows() == [ {"key1": "value1", "key2": "value2"}, ] @@ -333,7 +333,7 @@ def test_csv_parser_write_skip_header(tmpdir): data = b"header1,header2\nvalue11,value12\nvalue21,value22" path = str(tmpdir.join("table.csv")) with TableResource(data=data, format="csv") as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] resource.write_table(path, dialect=Dialect(header=False)) with open(path, "rb") as file: assert file.read() == b"value11,value12\r\nvalue21,value22\r\n" diff --git a/frictionless/formats/excel/parsers/__spec__/test_xls.py b/frictionless/formats/excel/parsers/__spec__/test_xls.py index 73e5a02213..36fd4f09e0 100644 --- a/frictionless/formats/excel/parsers/__spec__/test_xls.py +++ b/frictionless/formats/excel/parsers/__spec__/test_xls.py @@ -11,7 +11,7 @@ def test_xls_parser(): with TableResource(path="data/table.xls") as table: - assert table.header == ["id", "name"] + assert table.header.field_names == ["id", "name"] assert table.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -21,7 +21,7 @@ def test_xls_parser(): @pytest.mark.vcr def test_xls_parser_remote(): with TableResource(path=BASEURL % "data/table.xls") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -32,7 +32,7 @@ def test_xls_parser_sheet_by_index(): path = "data/sheet2.xls" control = formats.ExcelControl(sheet=2) with TableResource(path=path, control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -51,7 +51,7 @@ def test_xls_parser_sheet_by_name(): path = "data/sheet2.xls" control = formats.ExcelControl(sheet="Sheet2") with TableResource(path=path, control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -91,7 +91,7 @@ def test_xls_parser_merged_cells_fill(): def test_xls_parser_with_boolean(): with TableResource(path="data/table-with-booleans.xls") as resource: - assert resource.header == ["id", "boolean"] + assert resource.header.field_names == ["id", "boolean"] assert resource.read_rows() == [ {"id": 1, "boolean": True}, {"id": 2, "boolean": False}, @@ -106,7 +106,7 @@ def test_xls_parser_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.xls"))) source.write(target) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -119,7 +119,7 @@ def test_xls_parser_write_sheet_name(tmpdir): target = TableResource(path=str(tmpdir.join("table.xls")), control=control) source.write(target) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -133,10 +133,10 @@ def test_xls_parser_write_skip_header(tmpdir): path = str(tmpdir.join("table.xls")) target = TableResource(path=path, dialect=dialect, control=control) with TableResource(data=data, format="csv") as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] resource.write_table(target) table = target.read_table() - assert table.header == ["field1", "field2"] + assert table.header.field_names == ["field1", "field2"] # Bugs diff --git a/frictionless/formats/excel/parsers/__spec__/test_xlsx.py b/frictionless/formats/excel/parsers/__spec__/test_xlsx.py index 0f250c1dc5..972126aa29 100644 --- a/frictionless/formats/excel/parsers/__spec__/test_xlsx.py +++ b/frictionless/formats/excel/parsers/__spec__/test_xlsx.py @@ -23,7 +23,7 @@ def test_xlsx_parser_table(): data = io.open("data/table.xlsx", mode="rb") with TableResource(data=data, format="xlsx") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1.0, "name": "english"}, {"id": 2.0, "name": "中国人"}, @@ -38,7 +38,7 @@ def test_xlsx_parser_table(): def test_xlsx_parser_remote(): path = BASEURL % "data/table.xlsx" with TableResource(path=path) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1.0, "name": "english"}, {"id": 2.0, "name": "中国人"}, @@ -49,7 +49,7 @@ def test_xlsx_parser_sheet_by_index(): path = "data/sheet2.xlsx" control = formats.ExcelControl(sheet=2) with TableResource(path=path, control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1.0, "name": "english"}, {"id": 2.0, "name": "中国人"}, @@ -71,7 +71,7 @@ def test_xlsx_parser_sheet_by_name(): path = "data/sheet2.xlsx" control = formats.ExcelControl(sheet="Sheet2") with TableResource(path=path, control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1.0, "name": "english"}, {"id": 2.0, "name": "中国人"}, @@ -234,7 +234,7 @@ def test_xlsx_parser_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.xlsx"))) source.write(target) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -247,7 +247,7 @@ def test_xlsx_parser_write_sheet_name(tmpdir): target = TableResource(path=str(tmpdir.join("table.xlsx")), control=control) source.write(target) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -261,10 +261,10 @@ def test_xlsx_parser_write_skip_header(tmpdir): path = str(tmpdir.join("table.xlsx")) target = TableResource(path=path, dialect=dialect, control=control) with TableResource(data=data, format="csv") as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] resource.write_table(target) table = target.read_table() - assert table.header == ["field1", "field2"] + assert table.header.field_names == ["field1", "field2"] # Bugs @@ -275,7 +275,7 @@ def test_xlsx_parser_multiline_header_with_merged_cells_issue_1024(): dialect = Dialect(header_rows=[10, 11, 12], controls=[control]) with TableResource(path="data/issue-1024.xlsx", dialect=dialect) as resource: assert resource.header - assert resource.header[21] == "Current Phase P3+ #" + assert resource.header.field_names[21] == "Current Phase P3+ #" def test_xlsx_parser_stats_no_bytes_and_hash_issue_938(): diff --git a/frictionless/formats/gsheets/__spec__/test_parser.py b/frictionless/formats/gsheets/__spec__/test_parser.py index 815167fb45..589830b496 100644 --- a/frictionless/formats/gsheets/__spec__/test_parser.py +++ b/frictionless/formats/gsheets/__spec__/test_parser.py @@ -15,7 +15,7 @@ def test_gsheets_parser(): path = "https://docs.google.com/spreadsheets/d/1mHIWnDvW9cALRMq9OdNfRwjAthCUFUOACPp0Lkyl7b4/edit?usp=sharing" with TableResource(path=path) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -26,7 +26,7 @@ def test_gsheets_parser(): def test_gsheets_parser_with_gid(): path = "https://docs.google.com/spreadsheets/d/1mHIWnDvW9cALRMq9OdNfRwjAthCUFUOACPp0Lkyl7b4/edit#gid=960698813" with TableResource(path=path) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 2, "name": "中国人"}, {"id": 3, "name": "german"}, @@ -54,7 +54,7 @@ def test_gsheets_parser_write(google_credentials_path): source = TableResource(path="data/table.csv") target = source.write(path=path, control=control) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/html/__spec__/test_parser.py b/frictionless/formats/html/__spec__/test_parser.py index 225cd22180..3dfb1367ae 100644 --- a/frictionless/formats/html/__spec__/test_parser.py +++ b/frictionless/formats/html/__spec__/test_parser.py @@ -18,7 +18,7 @@ def test_html_parser(path, selector): control = formats.HtmlControl(selector=selector) with TableResource(path=path, control=control) as resource: assert resource.format == "html" - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -34,7 +34,7 @@ def test_html_parser_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.html"))) source.write(target) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -48,7 +48,7 @@ def test_html_parser_newline_in_cell_issue_865(tmpdir): source = TableResource(path="data/table-with-newline.html") target = source.write(str(tmpdir.join("table.csv"))) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "with newline"}, {"id": 2, "name": "with newline"}, diff --git a/frictionless/formats/inline/__spec__/test_parser.py b/frictionless/formats/inline/__spec__/test_parser.py index 829c695b2a..6a2b3da137 100644 --- a/frictionless/formats/inline/__spec__/test_parser.py +++ b/frictionless/formats/inline/__spec__/test_parser.py @@ -12,7 +12,7 @@ def test_inline_parser(): data = [["id", "name"], ["1", "english"], ["2", "中国人"]] with TableResource(data=data) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -23,7 +23,7 @@ def test_inline_parser_keyed(): data = [{"id": "1", "name": "english"}, {"id": "2", "name": "中国人"}] with TableResource(data=data, format="inline") as resource: assert resource.dialect.to_descriptor() == {"inline": {"keyed": True}} - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -34,7 +34,7 @@ def test_inline_parser_keyed_order_is_preserved(): data = [{"name": "english", "id": "1"}, {"name": "中国人", "id": "2"}] with TableResource(data=data, format="inline") as resource: assert resource.dialect.to_descriptor() == {"inline": {"keyed": True}} - assert resource.header == ["name", "id"] + assert resource.header.field_names == ["name", "id"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -48,7 +48,7 @@ def test_inline_parser_keyed_with_keys_provided(): assert resource.dialect.to_descriptor() == { "inline": {"keyed": True, "keys": ["name", "id"]} } - assert resource.header == ["name", "id"] + assert resource.header.field_names == ["name", "id"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -62,7 +62,7 @@ def data(): yield ["2", "中国人"] with TableResource(data=data) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -76,7 +76,7 @@ def data(): yield ["2", "中国人"] with TableResource(data=data()) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -91,7 +91,7 @@ def test_inline_parser_from_ordered_dict(): with TableResource(data=data) as resource: rows = resource.read_rows() assert resource.dialect.to_descriptor() == {"inline": {"keyed": True}} - assert resource.header == ["name", "id"] + assert resource.header.field_names == ["name", "id"] assert rows[0].cells == ["english", "1"] assert rows[1].cells == ["中国人", "2"] diff --git a/frictionless/formats/json/parsers/__spec__/test_json.py b/frictionless/formats/json/parsers/__spec__/test_json.py index cf473ca415..d64053f829 100644 --- a/frictionless/formats/json/parsers/__spec__/test_json.py +++ b/frictionless/formats/json/parsers/__spec__/test_json.py @@ -15,7 +15,7 @@ def test_json_parser(): with TableResource(path="data/table.json") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -25,7 +25,7 @@ def test_json_parser(): def test_json_parser_keyed(): with TableResource(path="data/table.keyed.json") as resource: assert resource.dialect.to_descriptor() == {"json": {"keyed": True}} - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -38,7 +38,7 @@ def test_json_parser_keyed_with_keys_provided(): assert resource.dialect.to_descriptor() == { "json": {"keyed": True, "keys": ["name", "id"]} } - assert resource.header == ["name", "id"] + assert resource.header.field_names == ["name", "id"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -48,7 +48,7 @@ def test_json_parser_keyed_with_keys_provided(): def test_json_parser_from_buffer(): data = '[["id", "name"], [1, "english"], [2, "中国人"]]'.encode("utf-8") with TableResource(data=data, format="json") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -59,7 +59,7 @@ def test_json_parser_from_buffer_keyed(): data = '[{"id": 1, "name": "english" }, {"id": 2, "name": "中国人" }]'.encode("utf-8") with TableResource(data=data, format="json") as resource: assert resource.dialect.to_descriptor() == {"json": {"keyed": True}} - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -70,7 +70,7 @@ def test_json_parser_from_buffer_keyed(): @pytest.mark.skipif(sys.version_info < (3, 10), reason="pytest-vcr bug in Python3.8/9") def test_json_parser_from_remote(): with TableResource(path=BASEURL % "data/table.json") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -82,7 +82,7 @@ def test_json_parser_from_remote(): def test_json_parser_from_remote_keyed(): with TableResource(path=BASEURL % "data/table.keyed.json") as resource: assert resource.dialect.to_descriptor() == {"json": {"keyed": True}} - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/json/parsers/__spec__/test_jsonl.py b/frictionless/formats/json/parsers/__spec__/test_jsonl.py index b29cb9339d..78cd82bb32 100644 --- a/frictionless/formats/json/parsers/__spec__/test_jsonl.py +++ b/frictionless/formats/json/parsers/__spec__/test_jsonl.py @@ -7,7 +7,7 @@ def test_jsonl_parser(): with TableResource(path="data/table.jsonl") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -16,7 +16,7 @@ def test_jsonl_parser(): def test_jsonl_parser_ndjson(): with TableResource(path="data/table.ndjson") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -30,7 +30,7 @@ def test_jsonl_parser_write(tmpdir): source = TableResource(path="data/table.csv") target = source.write(path=str(tmpdir.join("table.jsonl"))) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -42,7 +42,7 @@ def test_jsonl_parser_write_keyed(tmpdir): source = TableResource(path="data/table.csv") target = source.write(path=str(tmpdir.join("table.jsonl")), control=control) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/ods/__spec__/test_parser.py b/frictionless/formats/ods/__spec__/test_parser.py index 1ab6d564a8..ae0ec74be5 100644 --- a/frictionless/formats/ods/__spec__/test_parser.py +++ b/frictionless/formats/ods/__spec__/test_parser.py @@ -15,7 +15,7 @@ def test_ods_parser(): with TableResource(path="data/table.ods") as resource: assert resource.format == "ods" - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -26,7 +26,7 @@ def test_ods_parser(): def test_ods_parser_remote(): path = BASEURL % "data/table.ods" with TableResource(path=path) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -36,7 +36,7 @@ def test_ods_parser_remote(): def test_ods_parser_sheet_by_index(): control = formats.OdsControl(sheet=1) with TableResource(path="data/table.ods", control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -48,7 +48,7 @@ def test_ods_parser_2nd_sheet_by_index(): with TableResource( path="data/table-with-two-sheets.ods", control=control ) as resource: - assert resource.header == ["a", "b", "c"] + assert resource.header.field_names == ["a", "b", "c"] assert resource.read_rows() == [{"a": 1, "b": 2, "c": 3}] @@ -65,7 +65,7 @@ def test_ods_parser_sheet_by_index_not_existent(): def test_ods_parser_sheet_by_name(): control = formats.OdsControl(sheet="Лист1") with TableResource(path="data/table.ods", control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -77,7 +77,7 @@ def test_ods_parser_2nd_sheet_by_name(): with TableResource( path="data/table-with-two-sheets.ods", control=control ) as resource: - assert resource.header == ["a", "b", "c"] + assert resource.header.field_names == ["a", "b", "c"] assert resource.read_rows() == [{"a": 1, "b": 2, "c": 3}] @@ -95,7 +95,7 @@ def test_ods_parser_sheet_by_name_not_existent(): def test_ods_parser_with_boolean(): with TableResource(path="data/table-with-booleans.ods") as resource: - assert resource.header == ["id", "boolean"] + assert resource.header.field_names == ["id", "boolean"] assert resource.read_rows() == [ {"id": 1, "boolean": True}, {"id": 2, "boolean": False}, @@ -121,7 +121,7 @@ def test_ods_parser_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.ods"))) source.write(target) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -135,7 +135,7 @@ def test_ods_parser_write_skip_header(tmpdir): path = str(tmpdir.join("table.ods")) target = TableResource(path=path, dialect=dialect, control=control) with TableResource(data=data, format="csv") as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] resource.write_table(target) table = target.read_table() - assert table.header == ["field1", "field2"] + assert table.header.field_names == ["field1", "field2"] diff --git a/frictionless/formats/pandas/__spec__/test_parser.py b/frictionless/formats/pandas/__spec__/test_parser.py index 8ca453dd45..2935026984 100644 --- a/frictionless/formats/pandas/__spec__/test_parser.py +++ b/frictionless/formats/pandas/__spec__/test_parser.py @@ -44,7 +44,7 @@ def test_pandas_parser(): dataframe = pd.DataFrame(data=tc["df_data"]) with TableResource(data=dataframe) as resource: - assert resource.header == tc["expected_header"], tc["name"] + assert resource.header.field_names == tc["expected_header"], tc["name"] assert resource.read_rows() == tc["expected_rows"], tc["name"] diff --git a/frictionless/formats/parquet/__spec__/test_parser.py b/frictionless/formats/parquet/__spec__/test_parser.py index 76b39efda0..f72af811f8 100644 --- a/frictionless/formats/parquet/__spec__/test_parser.py +++ b/frictionless/formats/parquet/__spec__/test_parser.py @@ -10,7 +10,7 @@ def test_parquet_parser(): with TableResource(path="data/table.parq") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -19,7 +19,7 @@ def test_parquet_parser(): def test_parquet_parser_parquet_extension(): with TableResource(path="data/table.parquet") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -29,7 +29,7 @@ def test_parquet_parser_parquet_extension(): def test_parquet_parser_columns(): control = formats.ParquetControl(columns=["id"]) with TableResource(path="data/table.parq", control=control) as resource: - assert resource.header == ["id"] + assert resource.header.field_names == ["id"] assert resource.read_rows() == [ {"id": 1}, {"id": 2}, @@ -56,7 +56,7 @@ def test_parquet_parser_write(tmpdir): source.write(target) with target: assert target.format == "parq" - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -69,7 +69,7 @@ def test_parquet_parser_write_datetime_field_with_timezone(tmpdir): source.write(target) with target: assert target.format == "parq" - assert target.header == ["datetimewithtimezone"] + assert target.header.field_names == ["datetimewithtimezone"] assert target.read_rows() == [ { "datetimewithtimezone": datetime.datetime( diff --git a/frictionless/formats/spss/__spec__/test_parser.py b/frictionless/formats/spss/__spec__/test_parser.py index 3cc4c190b3..feffb62f92 100644 --- a/frictionless/formats/spss/__spec__/test_parser.py +++ b/frictionless/formats/spss/__spec__/test_parser.py @@ -19,7 +19,7 @@ def test_spss_parser_write(tmpdir): source = TableResource(path="data/table.csv") target = source.write(str(tmpdir.join("table.sav"))) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -110,7 +110,7 @@ def test_spss_parser_write_timezone(tmpdir): source = TableResource(path="data/timezone.csv") target = source.write(path=str(tmpdir.join("table.sav"))) with target: - assert target.header == ["datetime", "time"] + assert target.header.field_names == ["datetime", "time"] assert target.read_rows() == [ { "datetime": datetime(2020, 1, 1, 15), diff --git a/frictionless/formats/sql/__spec__/duckdb/test_parser.py b/frictionless/formats/sql/__spec__/duckdb/test_parser.py index 1393408e90..3c78fc64fc 100644 --- a/frictionless/formats/sql/__spec__/duckdb/test_parser.py +++ b/frictionless/formats/sql/__spec__/duckdb/test_parser.py @@ -27,7 +27,7 @@ def test_sql_parser(duckdb_url_data): # https://github.com/Mause/duckdb_engine/blob/71b1ed2f63dc25a848995986401be765711d763d/duckdb_engine/__init__.py#L159 # "primaryKey": ["id"], } - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -37,7 +37,7 @@ def test_sql_parser(duckdb_url_data): def test_sql_parser_order_by(duckdb_url_data): control = formats.SqlControl(table="table", order_by="id") with TableResource(path=duckdb_url_data, control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -47,7 +47,7 @@ def test_sql_parser_order_by(duckdb_url_data): def test_sql_parser_order_by_desc(duckdb_url_data): control = formats.SqlControl(table="table", order_by="id desc") with TableResource(path=duckdb_url_data, control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 2, "name": "中国人"}, {"id": 1, "name": "english"}, @@ -57,7 +57,7 @@ def test_sql_parser_order_by_desc(duckdb_url_data): def test_sql_parser_where(duckdb_url_data): control = formats.SqlControl(table="table", where="name = '中国人'") with TableResource(path=duckdb_url_data, control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 2, "name": "中国人"}, ] @@ -93,7 +93,7 @@ def test_sql_parser_write(duckdb_url_data): control = formats.SqlControl(table="name", order_by="id") target = source.write(path=duckdb_url_data, control=control) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -105,7 +105,7 @@ def test_sql_parser_write_where(duckdb_url_data): control = formats.SqlControl(table="name", where="name = '中国人'") target = source.write(path=duckdb_url_data, control=control) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 2, "name": "中国人"}, ] @@ -116,7 +116,7 @@ def test_sql_parser_write_timezone(sqlite_url): control = formats.SqlControl(table="timezone") target = source.write(path=sqlite_url, control=control) with target: - assert target.header == ["datetime", "time"] + assert target.header.field_names == ["datetime", "time"] assert target.read_rows() == [ { "datetime": datetime(2020, 1, 1, 15), @@ -148,7 +148,7 @@ def test_sql_parser_write_string_pk_issue_777_sqlite(sqlite_url): target = source.write(path=sqlite_url, control=control) with target: assert target.schema.primary_key == ["name"] - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/sql/__spec__/mysql/test_parser.py b/frictionless/formats/sql/__spec__/mysql/test_parser.py index c95b61b2fb..966685f977 100644 --- a/frictionless/formats/sql/__spec__/mysql/test_parser.py +++ b/frictionless/formats/sql/__spec__/mysql/test_parser.py @@ -15,7 +15,7 @@ def test_sql_parser_write_timezone_mysql(mysql_url): control = formats.SqlControl(table="timezone") target = source.write(path=mysql_url, control=control) with target: - assert target.header == ["datetime", "time"] + assert target.header.field_names == ["datetime", "time"] assert target.read_rows() == [ { "datetime": datetime(2020, 1, 1, 15), @@ -50,7 +50,7 @@ def test_sql_parser_write_string_pk_issue_777_mysql(mysql_url): target = source.write(path=mysql_url, control=control) with target: assert target.schema.primary_key == ["name"] - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/sql/__spec__/postgresql/test_parser.py b/frictionless/formats/sql/__spec__/postgresql/test_parser.py index 6e8f7acc33..8aa0002e7e 100644 --- a/frictionless/formats/sql/__spec__/postgresql/test_parser.py +++ b/frictionless/formats/sql/__spec__/postgresql/test_parser.py @@ -23,7 +23,7 @@ def test_sql_parser_write_timezone_postgresql(postgresql_url): control = formats.SqlControl(table="timezone") target = source.write(postgresql_url, control=control) with target: - assert target.header == ["datetime", "time"] + assert target.header.field_names == ["datetime", "time"] assert target.read_rows() == [ { "datetime": datetime(2020, 1, 1, 15), @@ -57,7 +57,7 @@ def test_sql_parser_write_string_pk_issue_777_postgresql(postgresql_url): target = source.write(postgresql_url, control=control) with target: assert target.schema.primary_key == ["name"] - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/sql/__spec__/test_parser.py b/frictionless/formats/sql/__spec__/test_parser.py index 996fee9ffc..6220640986 100644 --- a/frictionless/formats/sql/__spec__/test_parser.py +++ b/frictionless/formats/sql/__spec__/test_parser.py @@ -18,7 +18,7 @@ def test_sql_parser(sqlite_url_data): ], "primaryKey": ["id"], } - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -28,7 +28,7 @@ def test_sql_parser(sqlite_url_data): def test_sql_parser_order_by(sqlite_url_data): control = formats.SqlControl(table="table", order_by="id") with TableResource(path=sqlite_url_data, control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -38,7 +38,7 @@ def test_sql_parser_order_by(sqlite_url_data): def test_sql_parser_order_by_desc(sqlite_url_data): control = formats.SqlControl(table="table", order_by="id desc") with TableResource(path=sqlite_url_data, control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 2, "name": "中国人"}, {"id": 1, "name": "english"}, @@ -48,7 +48,7 @@ def test_sql_parser_order_by_desc(sqlite_url_data): def test_sql_parser_where(sqlite_url_data): control = formats.SqlControl(table="table", where="name = '中国人'") with TableResource(path=sqlite_url_data, control=control) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 2, "name": "中国人"}, ] @@ -84,7 +84,7 @@ def test_sql_parser_write(sqlite_url_data): control = formats.SqlControl(table="name", order_by="id") target = source.write(path=sqlite_url_data, control=control) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -96,7 +96,7 @@ def test_sql_parser_write_where(sqlite_url_data): control = formats.SqlControl(table="name", where="name = '中国人'") target = source.write(path=sqlite_url_data, control=control) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 2, "name": "中国人"}, ] @@ -107,7 +107,7 @@ def test_sql_parser_write_timezone(sqlite_url): control = formats.SqlControl(table="timezone") target = source.write(path=sqlite_url, control=control) with target: - assert target.header == ["datetime", "time"] + assert target.header.field_names == ["datetime", "time"] assert target.read_rows() == [ { "datetime": datetime(2020, 1, 1, 15), @@ -139,7 +139,7 @@ def test_sql_parser_write_string_pk_issue_777_sqlite(sqlite_url): target = source.write(path=sqlite_url, control=control) with target: assert target.schema.primary_key == ["name"] - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/yaml/__spec__/test_parser.py b/frictionless/formats/yaml/__spec__/test_parser.py index 186eab9423..4c0c9d6129 100644 --- a/frictionless/formats/yaml/__spec__/test_parser.py +++ b/frictionless/formats/yaml/__spec__/test_parser.py @@ -6,7 +6,7 @@ def test_yaml_parser(): with TableResource(path="data/table.yaml") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -15,7 +15,7 @@ def test_yaml_parser(): def test_yaml_parser_keyed(): with TableResource(path="data/table.keyed.yaml") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -31,7 +31,7 @@ def test_yaml_parser_write(tmpdir): source.write(target) with target: assert target.format == "yaml" - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -42,7 +42,7 @@ def test_yaml_parser_write_skip_header(tmpdir): dialect = Dialect.from_descriptor({"header": False}) target = TableResource(path=str(tmpdir.join("table.yaml")), dialect=dialect) with TableResource(path="data/table.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] resource.write_table(target) assert target.read_rows() == [ {"field1": 1, "field2": "english"}, diff --git a/frictionless/package/__spec__/test_resources.py b/frictionless/package/__spec__/test_resources.py index 037c2fba90..84dfda921e 100644 --- a/frictionless/package/__spec__/test_resources.py +++ b/frictionless/package/__spec__/test_resources.py @@ -105,4 +105,4 @@ def test_package_resources_respect_layout_set_after_creation_issue_503(): resource = package.get_table_resource("table") resource.dialect = Dialect(comment_rows=[3]) assert resource.read_rows() == [{"id": 1, "name": "english"}] - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] diff --git a/frictionless/portals/ckan/__spec__/test_adapter.py b/frictionless/portals/ckan/__spec__/test_adapter.py index 95b955b2ad..57b5b7628f 100644 --- a/frictionless/portals/ckan/__spec__/test_adapter.py +++ b/frictionless/portals/ckan/__spec__/test_adapter.py @@ -556,7 +556,7 @@ def test_ckan_parser(options_lh): source = TableResource(path="data/table.csv") target = source.write(path=baseurl, control=control, format="csv") with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/resource/__spec__/test_validate.py b/frictionless/resource/__spec__/test_validate.py index bb111a52d8..5e052dc4d9 100644 --- a/frictionless/resource/__spec__/test_validate.py +++ b/frictionless/resource/__spec__/test_validate.py @@ -632,7 +632,7 @@ def test_resource_validate_detector_infer_names(): assert resource.schema.fields[1].name == "name" assert resource.stats.rows == 3 assert resource.labels == [] - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] # Encoding diff --git a/frictionless/resource/__spec__/test_validate_dialect.py b/frictionless/resource/__spec__/test_validate_dialect.py index 984999b0c9..6b252a6b84 100644 --- a/frictionless/resource/__spec__/test_validate_dialect.py +++ b/frictionless/resource/__spec__/test_validate_dialect.py @@ -20,7 +20,7 @@ def test_resource_validate_dialect_header_false(): assert report.task.stats.get("rows") == 3 assert resource.dialect.header is False assert resource.labels == [] - assert resource.header == ["field1", "field2"] + assert resource.header.field_names == ["field1", "field2"] def test_resource_validate_dialect_none_extra_cell(): @@ -30,7 +30,7 @@ def test_resource_validate_dialect_none_extra_cell(): assert report.task.stats.get("rows") == 3 assert resource.dialect.header is False assert resource.labels == [] - assert resource.header == ["field1", "field2"] + assert resource.header.field_names == ["field1", "field2"] assert report.flatten(["rowNumber", "fieldNumber", "type"]) == [ [3, 3, "extra-cell"], ] @@ -40,7 +40,7 @@ def test_resource_validate_dialect_number(): dialect = Dialect(header_rows=[2]) resource = TableResource(path="data/matrix.csv", dialect=dialect) report = resource.validate() - assert resource.header == ["11", "12", "13", "14"] + assert resource.header.field_names == ["11", "12", "13", "14"] assert report.valid @@ -48,7 +48,7 @@ def test_resource_validate_dialect_list_of_numbers(): dialect = Dialect(header_rows=[2, 3, 4]) resource = TableResource(path="data/matrix.csv", dialect=dialect) report = resource.validate() - assert resource.header == ["11 21 31", "12 22 32", "13 23 33", "14 24 34"] + assert resource.header.field_names == ["11 21 31", "12 22 32", "13 23 33", "14 24 34"] assert report.valid @@ -56,7 +56,7 @@ def test_resource_validate_dialect_list_of_numbers_and_headers_join(): dialect = Dialect(header_rows=[2, 3, 4], header_join=".") resource = TableResource(path="data/matrix.csv", dialect=dialect) report = resource.validate() - assert resource.header == ["11.21.31", "12.22.32", "13.23.33", "14.24.34"] + assert resource.header.field_names == ["11.21.31", "12.22.32", "13.23.33", "14.24.34"] assert report.valid @@ -64,7 +64,7 @@ def test_resource_validate_dialect_skip_rows(): dialect = Dialect(comment_char="41", comment_rows=[2]) resource = TableResource(path="data/matrix.csv", dialect=dialect) report = resource.validate() - assert resource.header == ["f1", "f2", "f3", "f4"] + assert resource.header.field_names == ["f1", "f2", "f3", "f4"] assert report.task.stats.get("rows") == 2 assert report.task.valid diff --git a/frictionless/resource/resource.py b/frictionless/resource/resource.py index dfe0fb410b..f9940617f9 100644 --- a/frictionless/resource/resource.py +++ b/frictionless/resource/resource.py @@ -37,7 +37,7 @@ class Resource(Metadata, metaclass=Factory): # type: ignore ```python with Resource("data/table.csv") as resource: - resource.header == ["id", "name"] + resource.header.field_names == ["id", "name"] resource.read_rows() == [ {'id': 1, 'name': 'english'}, {'id': 2, 'name': '中国人'}, diff --git a/frictionless/resources/__spec__/table/test_compression.py b/frictionless/resources/__spec__/table/test_compression.py index 8647483257..3b3bb5a2d7 100644 --- a/frictionless/resources/__spec__/table/test_compression.py +++ b/frictionless/resources/__spec__/table/test_compression.py @@ -12,7 +12,7 @@ def test_resource_compression_local_csv_zip(): with TableResource(path="data/table.csv.zip") as resource: assert resource.innerpath == "table.csv" assert resource.compression == "zip" - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -23,7 +23,7 @@ def test_resource_compression_local_csv_zip_multiple_files(): with TableResource(path="data/table-multiple-files.zip", format="csv") as resource: assert resource.innerpath == "table-reverse.csv" assert resource.compression == "zip" - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "中国人"}, {"id": 2, "name": "english"}, @@ -35,7 +35,7 @@ def test_resource_compression_local_csv_zip_multiple_open(): # Open first time resource.open() - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -44,7 +44,7 @@ def test_resource_compression_local_csv_zip_multiple_open(): # Open second time resource.open() - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -56,7 +56,7 @@ def test_resource_compression_local_csv_gz(): with TableResource(path="data/table.csv.gz") as resource: assert resource.compression == "gz" assert resource.innerpath is None - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -67,7 +67,7 @@ def test_resource_compression_local_csv_xz(): with TableResource(path="data/table.csv.xz") as resource: assert resource.compression == "xz" assert resource.innerpath is None - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -78,7 +78,7 @@ def test_resource_compression_local_csv_bz2(): with TableResource(path="data/table.csv.bz2") as resource: assert resource.compression == "bz2" assert resource.innerpath is None - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -88,7 +88,7 @@ def test_resource_compression_local_csv_bz2(): def test_resource_compression_stream_csv_zip(): with open("data/table.csv.zip", "rb") as file: with TableResource(data=file, format="csv", compression="zip") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -98,7 +98,7 @@ def test_resource_compression_stream_csv_zip(): def test_resource_compression_stream_csv_gz(): with open("data/table.csv.gz", "rb") as file: with TableResource(data=file, format="csv", compression="gz") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -108,7 +108,7 @@ def test_resource_compression_stream_csv_gz(): def test_resource_compression_stream_csv_xz(): with open("data/table.csv.xz", "rb") as file: with TableResource(data=file, format="csv", compression="xz") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -118,7 +118,7 @@ def test_resource_compression_stream_csv_xz(): def test_resource_compression_stream_csv_bz2(): with open("data/table.csv.bz2", "rb") as file: with TableResource(data=file, format="csv", compression="bz2") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -129,7 +129,7 @@ def test_resource_compression_stream_csv_bz2(): def test_resource_compression_remote_csv_zip(): source = "https://raw.githubusercontent.com/frictionlessdata/tabulator-py/master/data/table.csv.zip" with TableResource(path=source) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -140,7 +140,7 @@ def test_resource_compression_remote_csv_zip(): def test_resource_compression_remote_csv_gz(): source = "https://raw.githubusercontent.com/frictionlessdata/tabulator-py/master/data/table.csv.gz" with TableResource(path=source) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -186,7 +186,7 @@ def test_resource_compression_legacy_no_value_framework_v4_issue_616(): with TableResource.from_descriptor(descriptor) as resource: assert resource.innerpath is None assert resource.compression is None - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/resources/__spec__/table/test_detector.py b/frictionless/resources/__spec__/table/test_detector.py index e3e2f250b4..2cbaf1e365 100644 --- a/frictionless/resources/__spec__/table/test_detector.py +++ b/frictionless/resources/__spec__/table/test_detector.py @@ -10,7 +10,7 @@ def test_resource_detector_encoding_function(): assert resource.encoding == "utf-8" assert resource.sample == [["id", "name"], ["1", "english"], ["2", "中国人"]] assert resource.fragment == [["1", "english"], ["2", "中国人"]] - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] def test_resource_detector_field_type(): @@ -23,7 +23,7 @@ def test_resource_detector_field_type(): {"name": "name", "type": "string"}, ] } - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": "1", "name": "english"}, {"id": "2", "name": "中国人"}, @@ -41,7 +41,7 @@ def test_resource_detector_field_names(): ] } assert resource.labels == ["id", "name"] - assert resource.header == ["new1", "new2"] + assert resource.header.field_names == ["new1", "new2"] assert resource.read_rows() == [ {"new1": 1, "new2": "english"}, {"new1": 2, "new2": "中国人"}, @@ -58,7 +58,7 @@ def test_resource_detector_field_float_numbers(): {"name": "number", "type": "number", "floatNumber": True}, ] } - assert resource.header == ["number"] + assert resource.header.field_names == ["number"] assert resource.read_rows() == [ {"number": 1.1}, {"number": 2.2}, @@ -69,7 +69,7 @@ def test_resource_detector_field_float_numbers(): def test_resource_detector_field_type_with_open(): detector = Detector(field_type="string") with TableResource(path="data/table.csv", detector=detector) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.schema.to_descriptor() == { "fields": [ {"name": "id", "type": "string"}, @@ -92,7 +92,7 @@ def test_resource_detector_field_names_with_open(): ] } assert resource.labels == ["id", "name"] - assert resource.header == ["new1", "new2"] + assert resource.header.field_names == ["new1", "new2"] assert resource.read_rows() == [ {"new1": 1, "new2": "english"}, {"new1": 2, "new2": "中国人"}, @@ -115,7 +115,7 @@ def test_resource_detector_schema_sync(): assert resource.schema == schema assert resource.sample == [["name", "id"], ["english", "1"], ["中国人", "2"]] assert resource.fragment == [["english", "1"], ["中国人", "2"]] - assert resource.header == ["name", "id"] + assert resource.header.field_names == ["name", "id"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -139,7 +139,7 @@ def test_resource_detector_schema_sync_with_infer(): assert resource.schema == schema assert resource.sample == [["name", "id"], ["english", "1"], ["中国人", "2"]] assert resource.fragment == [["english", "1"], ["中国人", "2"]] - assert resource.header == ["name", "id"] + assert resource.header.field_names == ["name", "id"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -156,7 +156,7 @@ def test_resource_detector_schema_patch(): ] } assert resource.labels == ["id", "name"] - assert resource.header == ["ID", "name"] + assert resource.header.field_names == ["ID", "name"] assert resource.read_rows() == [ {"ID": "1", "name": "english"}, {"ID": "2", "name": "中国人"}, @@ -166,7 +166,7 @@ def test_resource_detector_schema_patch(): def test_resource_detector_schema_patch_missing_values(): detector = Detector(schema_patch={"missingValues": ["1", "2"]}) with TableResource(path="data/table.csv", detector=detector) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.schema.to_descriptor() == { "fields": [ {"name": "id", "type": "integer"}, @@ -191,7 +191,7 @@ def test_resource_detector_schema_patch_with_infer(): ] } assert resource.labels == ["id", "name"] - assert resource.header == ["ID", "name"] + assert resource.header.field_names == ["ID", "name"] assert resource.read_rows() == [ {"ID": "1", "name": "english"}, {"ID": "2", "name": "中国人"}, diff --git a/frictionless/resources/__spec__/table/test_dialect.py b/frictionless/resources/__spec__/table/test_dialect.py index 002c1b26df..c182a77a7b 100644 --- a/frictionless/resources/__spec__/table/test_dialect.py +++ b/frictionless/resources/__spec__/table/test_dialect.py @@ -9,7 +9,7 @@ def test_resource_dialect_header(): with TableResource(path="data/table.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -36,7 +36,7 @@ def test_resource_dialect_header_false(): def test_resource_dialect_header_unicode(): with TableResource(path="data/table-unicode-headers.csv") as resource: - assert resource.header == ["id", "国人"] + assert resource.header.field_names == ["id", "国人"] assert resource.read_rows() == [ {"id": 1, "国人": "english"}, {"id": 2, "国人": "中国人"}, @@ -46,7 +46,7 @@ def test_resource_dialect_header_unicode(): def test_resource_dialect_header_stream_context_manager(): source = open("data/table.csv", mode="rb") with TableResource(data=source, format="csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -57,7 +57,7 @@ def test_resource_dialect_header_inline(): source = [[], ["id", "name"], ["1", "english"], ["2", "中国人"]] dialect = Dialect(header_rows=[2]) with TableResource(data=source, dialect=dialect) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -67,7 +67,7 @@ def test_resource_dialect_header_inline(): def test_resource_dialect_header_json_keyed(): data = "[" '{"id": 1, "name": "english"},' '{"id": 2, "name": "中国人"}]' with resources.TableResource(data=data.encode("utf-8"), format="json") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -77,7 +77,7 @@ def test_resource_dialect_header_json_keyed(): def test_resource_dialect_header_inline_keyed(): source = [{"id": "1", "name": "english"}, {"id": "2", "name": "中国人"}] with TableResource(data=source) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -89,7 +89,7 @@ def test_resource_dialect_header_inline_keyed_headers_is_none(): dialect = Dialect(header=False) with TableResource(data=source, dialect=dialect) as resource: assert resource.labels == [] - assert resource.header == ["field1", "field2"] + assert resource.header.field_names == ["field1", "field2"] assert resource.read_rows() == [ {"field1": "id", "field2": "name"}, {"field1": "1", "field2": "english"}, @@ -102,7 +102,7 @@ def test_resource_dialect_header_xlsx_multiline(): control = Control.from_descriptor({"type": "excel", "fillMergedCells": True}) dialect = Dialect(header_rows=[1, 2, 3, 4, 5], controls=[control]) with TableResource(path=source, dialect=dialect) as resource: - header = resource.header + header = resource.header.field_names assert header == [ "Region", "Caloric contribution (%)", @@ -118,7 +118,7 @@ def test_resource_dialect_header_csv_multiline_headers_join(): source = b"k1\nk2\nv1\nv2\nv3" dialect = Dialect(header_rows=[1, 2], header_join=":") with TableResource(data=source, format="csv", dialect=dialect) as resource: - assert resource.header == ["k1:k2"] + assert resource.header.field_names == ["k1:k2"] assert resource.read_rows() == [ {"k1:k2": "v1"}, {"k1:k2": "v2"}, @@ -130,7 +130,7 @@ def test_resource_dialect_header_csv_multiline_headers_duplicates(): source = b"k1\nk1\nv1\nv2\nv3" dialect = Dialect(header_rows=[1, 2]) with TableResource(data=source, format="csv", dialect=dialect) as resource: - assert resource.header == ["k1"] + assert resource.header.field_names == ["k1"] assert resource.read_rows() == [ {"k1": "v1"}, {"k1": "v2"}, @@ -143,7 +143,7 @@ def test_resource_dialect_header_strip_and_non_strings(): dialect = Dialect(header_rows=[1]) with TableResource(data=source, dialect=dialect) as resource: assert resource.labels == ["header", "2", "3", ""] - assert resource.header == ["header", "2", "3", "field4"] + assert resource.header.field_names == ["header", "2", "3", "field4"] assert resource.read_rows() == [ {"header": "value1", "2": "value2", "3": "value3", "field4": "value4"}, ] @@ -154,7 +154,7 @@ def test_resource_layout_header_case_default(): with TableResource(path="data/table.csv", schema=schema) as resource: assert resource.schema.field_names == ["ID", "NAME"] assert resource.labels == ["id", "name"] - assert resource.header == ["ID", "NAME"] + assert resource.header.field_names == ["ID", "NAME"] assert resource.header.valid is False assert resource.header.errors[0].type == "incorrect-label" assert resource.header.errors[1].type == "incorrect-label" @@ -166,7 +166,7 @@ def test_resource_layout_header_case_is_false(): with TableResource(path="data/table.csv", dialect=dialect, schema=schema) as resource: assert resource.schema.field_names == ["ID", "NAME"] assert resource.labels == ["id", "name"] - assert resource.header == ["ID", "NAME"] + assert resource.header.field_names == ["ID", "NAME"] assert resource.header.valid is True @@ -174,7 +174,7 @@ def test_resource_dialect_skip_rows(): source = "data/skip-rows.csv" dialect = Dialect(comment_char="#", comment_rows=[5]) with TableResource(path=source, dialect=dialect) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, ] @@ -184,7 +184,7 @@ def test_resource_dialect_skip_rows_with_headers(): source = "data/skip-rows.csv" dialect = Dialect(comment_char="#") with TableResource(path=source, dialect=dialect) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -195,7 +195,7 @@ def test_resource_layout_skip_rows_with_headers_example_from_readme(): dialect = Dialect(comment_char="#") source = [["#comment"], ["name", "order"], ["John", 1], ["Alex", 2]] with TableResource(data=source, dialect=dialect) as resource: - assert resource.header == ["name", "order"] + assert resource.header.field_names == ["name", "order"] assert resource.read_rows() == [ {"name": "John", "order": 1}, {"name": "Alex", "order": 2}, @@ -233,7 +233,7 @@ def test_resource_dialect_csv_default(): assert control.double_quote is True assert control.quote_char == '"' assert control.skip_initial_space is False - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.dialect.header is True assert resource.dialect.header_rows == [1] # TODO: review @@ -248,7 +248,7 @@ def test_resource_dialect_csv_default(): def test_resource_dialect_csv_delimiter(): with TableResource(path="data/delimiter.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.dialect.to_descriptor() == {"csv": {"delimiter": ";"}} assert resource.read_rows() == [ {"id": 1, "name": "english"}, @@ -260,7 +260,7 @@ def test_resource_dialect_json_property(): data = b'{"root": [["header1", "header2"], ["value1", "value2"]]}' dialect = Dialect.from_descriptor({"json": {"property": "root"}}) with resources.TableResource(data=data, format="json", dialect=dialect) as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, ] diff --git a/frictionless/resources/__spec__/table/test_encoding.py b/frictionless/resources/__spec__/table/test_encoding.py index f0e75455fe..8e6dc35df9 100644 --- a/frictionless/resources/__spec__/table/test_encoding.py +++ b/frictionless/resources/__spec__/table/test_encoding.py @@ -9,7 +9,7 @@ def test_resource_encoding(): with TableResource(path="data/table.csv") as resource: assert resource.encoding == "utf-8" - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -19,7 +19,7 @@ def test_resource_encoding(): def test_resource_encoding_explicit_utf8(): with TableResource(path="data/table.csv", encoding="utf-8") as resource: assert resource.encoding == "utf-8" - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -29,7 +29,7 @@ def test_resource_encoding_explicit_utf8(): def test_resource_encoding_explicit_latin1(): with TableResource(path="data/latin1.csv", encoding="latin1") as resource: assert resource.encoding == "iso8859-1" - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "©"}, diff --git a/frictionless/resources/__spec__/table/test_general.py b/frictionless/resources/__spec__/table/test_general.py index 3458988a62..29771482bc 100644 --- a/frictionless/resources/__spec__/table/test_general.py +++ b/frictionless/resources/__spec__/table/test_general.py @@ -127,7 +127,7 @@ def test_resource_source_path(): assert resource.sample == [["id", "name"], ["1", "english"], ["2", "中国人"]] assert resource.fragment == [["1", "english"], ["2", "中国人"]] assert resource.labels == ["id", "name"] - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] if not platform.type == "windows": assert resource.stats.md5 == "6c2c61dd9b0e9c6876139a449ed87933" assert ( @@ -193,7 +193,7 @@ def test_resource_source_data(): assert resource.sample == data assert resource.fragment == data[1:] assert resource.labels == ["id", "name"] - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.stats.fields == 2 assert resource.stats.rows == 2 @@ -241,7 +241,7 @@ def test_resource_skip_blank_at_the_end_issue_bco_dmo_33(): source = "data/skip-blank-at-the-end.csv" with TableResource(path=source, dialect=dialect) as resource: rows = resource.read_rows() - assert resource.header == ["test1", "test2"] + assert resource.header.field_names == ["test1", "test2"] assert rows[0].cells == ["1", "2"] assert rows[1].cells == [] @@ -271,7 +271,10 @@ def test_resource_skip_rows_non_string_cell_issue_320(): controls=[Control.from_descriptor({"type": "excel", "fillMergedCells": True})], ) with TableResource(path=source, dialect=dialect) as resource: - assert resource.header[7] == "Current Population Analysed % of total county Pop" + assert ( + resource.header.field_names[7] + == "Current Population Analysed % of total county Pop" + ) @pytest.mark.skipif(platform.type == "windows", reason="Fix on Windows") diff --git a/frictionless/resources/__spec__/table/test_innerpath.py b/frictionless/resources/__spec__/table/test_innerpath.py index 722a072b2e..0abd5f0f63 100644 --- a/frictionless/resources/__spec__/table/test_innerpath.py +++ b/frictionless/resources/__spec__/table/test_innerpath.py @@ -8,7 +8,7 @@ def test_resource_innerpath_local_csv_zip(): assert resource.place == "data/table.csv.zip -> table.csv" assert resource.compression == "zip" assert resource.innerpath == "table.csv" - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -20,7 +20,7 @@ def test_resource_innerpath_local_csv_zip_multiple_files(): assert resource.place == "data/table-multiple-files.zip -> table-reverse.csv" assert resource.compression == "zip" assert resource.innerpath == "table-reverse.csv" - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "中国人"}, {"id": 2, "name": "english"}, @@ -34,7 +34,7 @@ def test_resource_innerpath_local_csv_zip_multiple_files_explicit(): assert resource.place == "data/table-multiple-files.zip -> table.csv" assert resource.compression == "zip" assert resource.innerpath == "table.csv" - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/resources/__spec__/table/test_open.py b/frictionless/resources/__spec__/table/test_open.py index a2968d924f..3e7e9daa46 100644 --- a/frictionless/resources/__spec__/table/test_open.py +++ b/frictionless/resources/__spec__/table/test_open.py @@ -18,7 +18,7 @@ def test_resource_open(): assert resource.compression is None assert resource.sample == [["id", "name"], ["1", "english"], ["2", "中国人"]] assert resource.fragment == [["1", "english"], ["2", "中国人"]] - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.header.row_numbers == [1] assert resource.schema.to_descriptor() == { "fields": [ @@ -54,7 +54,7 @@ def test_resource_open_read_rows(): def test_resource_open_row_stream(): with TableResource(path="data/table.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert list(resource.row_stream) == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -64,7 +64,7 @@ def test_resource_open_row_stream(): def test_resource_open_row_stream_iterate(): with TableResource(path="data/table.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] for row in resource.row_stream: assert len(row) == 2 assert row.row_number in [2, 3] @@ -78,7 +78,7 @@ def test_resource_open_row_stream_error_cells(): detector = Detector(field_type="integer") with TableResource(path="data/table.csv", detector=detector) as resource: row1, row2 = resource.read_rows() - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert row1.errors[0].type == "type-error" assert row1.error_cells == {"name": "english"} assert row1.to_dict() == {"id": 1, "name": None} @@ -93,7 +93,7 @@ def test_resource_open_row_stream_blank_cells(): detector = Detector(schema_patch={"missingValues": ["1", "2"]}) with TableResource(path="data/table.csv", detector=detector) as resource: row1, row2 = resource.read_rows() - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert row1.blank_cells == {"id": "1"} assert row1.to_dict() == {"id": None, "name": "english"} assert row1.valid is True @@ -136,14 +136,14 @@ def test_resource_open_cell_stream_iterate(): def test_resource_open_empty(): with TableResource(path="data/empty.csv") as resource: assert resource.header.missing - assert resource.header == [] + assert resource.header.field_names == [] assert resource.schema.to_descriptor() == {"fields": []} assert resource.read_rows() == [] def test_resource_open_without_rows(): with TableResource(path="data/without-rows.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [] assert resource.schema.to_descriptor() == { "fields": [ @@ -158,7 +158,7 @@ def test_resource_open_without_headers(): with TableResource(path="data/without-headers.csv", dialect=dialect) as resource: assert resource.labels == [] assert resource.header.missing - assert resource.header == ["field1", "field2"] + assert resource.header.field_names == ["field1", "field2"] assert resource.schema.to_descriptor() == { "fields": [ {"name": "field1", "type": "integer"}, @@ -184,7 +184,7 @@ def test_resource_open_source_error_data(): def test_resource_reopen(): with TableResource(path="data/table.csv") as resource: # Open - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -192,7 +192,7 @@ def test_resource_reopen(): # Re-open resource.open() - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/resources/__spec__/table/test_schema.py b/frictionless/resources/__spec__/table/test_schema.py index a7883d1d19..c198c1be84 100644 --- a/frictionless/resources/__spec__/table/test_schema.py +++ b/frictionless/resources/__spec__/table/test_schema.py @@ -112,7 +112,7 @@ def test_resource_schema_from_path_error_bad_path(): def test_resource_schema_inferred(): with TableResource(path="data/table.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.schema.to_descriptor() == { "fields": [ {"name": "id", "type": "integer"}, @@ -136,7 +136,7 @@ def test_resource_schema_provided(): ) with TableResource(path="data/table.csv", schema=schema) as resource: assert resource.labels == ["id", "name"] - assert resource.header == ["new1", "new2"] + assert resource.header.field_names == ["new1", "new2"] assert resource.schema.to_descriptor() == { "fields": [ {"name": "new1", "type": "string"}, diff --git a/frictionless/resources/__spec__/table/test_write.py b/frictionless/resources/__spec__/table/test_write.py index 6938f03265..bac95fb058 100644 --- a/frictionless/resources/__spec__/table/test_write.py +++ b/frictionless/resources/__spec__/table/test_write.py @@ -12,7 +12,7 @@ def test_resource_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.csv"))) source.write(target) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -24,7 +24,7 @@ def test_resource_write_to_path(tmpdir): source = TableResource(path="data/table.csv") target = source.write(str(tmpdir.join("table.csv"))) with target: - assert target.header == ["id", "name"] + assert target.header.field_names == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/resources/table.py b/frictionless/resources/table.py index 306b360767..049909e106 100644 --- a/frictionless/resources/table.py +++ b/frictionless/resources/table.py @@ -629,7 +629,7 @@ def to_snap(self, *, json: bool = False): """ snap: List[List[Any]] = [] with helpers.ensure_open(self): - snap.append(self.header.to_list()) + snap.append(list(self.header.field_names)) for row in self.row_stream: snap.append(row.to_list(json=json)) return snap diff --git a/frictionless/schemes/aws/loaders/__spec__/test_s3.py b/frictionless/schemes/aws/loaders/__spec__/test_s3.py index 347bf3d0fe..dcb29affac 100644 --- a/frictionless/schemes/aws/loaders/__spec__/test_s3.py +++ b/frictionless/schemes/aws/loaders/__spec__/test_s3.py @@ -26,7 +26,7 @@ def test_s3_loader(bucket_name): # Read with TableResource(path="s3://%s/table.csv" % bucket_name) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -48,7 +48,7 @@ def test_s3_loader_write(bucket_name): # Read with TableResource(path="s3://%s/table.csv" % bucket_name) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -132,7 +132,7 @@ def test_s3_loader_problem_with_spaces_issue_501(bucket_name): # Read with TableResource(path="s3://%s/table with space.csv" % bucket_name) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/schemes/buffer/__spec__/test_loader.py b/frictionless/schemes/buffer/__spec__/test_loader.py index 85e1f9c5cb..807c1c17f9 100644 --- a/frictionless/schemes/buffer/__spec__/test_loader.py +++ b/frictionless/schemes/buffer/__spec__/test_loader.py @@ -9,7 +9,7 @@ def test_buffer_loader(): data = b"header1,header2\nvalue1,value2\nvalue3,value4" with TableResource(data=data, format="csv") as resource: - assert resource.header == ["header1", "header2"] + assert resource.header.field_names == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, diff --git a/frictionless/schemes/local/__spec__/test_loader.py b/frictionless/schemes/local/__spec__/test_loader.py index 944ea2727e..2a9a032983 100644 --- a/frictionless/schemes/local/__spec__/test_loader.py +++ b/frictionless/schemes/local/__spec__/test_loader.py @@ -8,7 +8,7 @@ def test_local_loader(): with TableResource(path="data/table.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -19,7 +19,7 @@ def test_local_loader_pathlib_path(): pathlib = import_module("pathlib") with Resource(pathlib.Path("data/table.csv")) as resource: assert isinstance(resource, TableResource) - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/schemes/multipart/__spec__/test_loader.py b/frictionless/schemes/multipart/__spec__/test_loader.py index e7aac1abfa..f3fb21d247 100644 --- a/frictionless/schemes/multipart/__spec__/test_loader.py +++ b/frictionless/schemes/multipart/__spec__/test_loader.py @@ -16,7 +16,7 @@ def test_multipart_loader(): with TableResource( path="data/chunk1.csv", extrapaths=["data/chunk2.csv"] ) as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -170,7 +170,7 @@ def test_multipart_loader_with_compressed_parts_issue_1215(): ) as resource: assert resource.innerpath is None assert resource.compression == "zip" - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/schemes/remote/__spec__/test_loader.py b/frictionless/schemes/remote/__spec__/test_loader.py index 7c4800b3a3..eeedeeb07f 100644 --- a/frictionless/schemes/remote/__spec__/test_loader.py +++ b/frictionless/schemes/remote/__spec__/test_loader.py @@ -15,7 +15,7 @@ @pytest.mark.skipif(sys.version_info < (3, 10), reason="pytest-vcr bug in Python3.8/9") def test_remote_loader(): with TableResource(path=BASEURL % "data/table.csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -57,7 +57,7 @@ def test_remote_loader_http_preload(): assert control.http_preload is True assert resource.sample == [["id", "name"], ["1", "english"], ["2", "中国人"]] assert resource.fragment == [["1", "english"], ["2", "中国人"]] - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] # Write diff --git a/frictionless/schemes/stream/__spec__/test_loader.py b/frictionless/schemes/stream/__spec__/test_loader.py index 291367a28f..931dcaeb39 100644 --- a/frictionless/schemes/stream/__spec__/test_loader.py +++ b/frictionless/schemes/stream/__spec__/test_loader.py @@ -9,7 +9,7 @@ def test_stream_loader(): with open("data/table.csv", mode="rb") as file: with TableResource(data=file, format="csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -19,7 +19,7 @@ def test_stream_loader(): def test_stream_loader_text_stream(): with open("data/table.csv") as file: with TableResource(data=file, format="csv") as resource: - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/steps/table/table_normalize.py b/frictionless/steps/table/table_normalize.py index 409d2a90ab..1943770db1 100644 --- a/frictionless/steps/table/table_normalize.py +++ b/frictionless/steps/table/table_normalize.py @@ -29,7 +29,7 @@ def transform_resource(self, resource: Resource): # Data def data(): # type: ignore with current: - yield current.header.to_list() # type: ignore + yield list(current.header.field_names) # type: ignore for row in current.row_stream: # type: ignore yield row.to_list() # type: ignore diff --git a/frictionless/system/__spec__/test_system.py b/frictionless/system/__spec__/test_system.py index 3f6d7a9d59..a2199e6bfe 100644 --- a/frictionless/system/__spec__/test_system.py +++ b/frictionless/system/__spec__/test_system.py @@ -21,5 +21,5 @@ def test_system_use_context_http_session(): with TableResource(path=BASEURL % "data/table.csv") as resource: control = resource.dialect.get_control("remote") assert isinstance(control, schemes.RemoteControl) - assert resource.header == ["id", "name"] + assert resource.header.field_names == ["id", "name"] assert system.http_session is not session diff --git a/frictionless/table/__spec__/test_header.py b/frictionless/table/__spec__/test_header.py index 4afb6e73a1..5ddd47faa9 100644 --- a/frictionless/table/__spec__/test_header.py +++ b/frictionless/table/__spec__/test_header.py @@ -11,19 +11,18 @@ def test_basic(): with TableResource(data=[["field1", "field2", "field3"], [1, 2, 3]]) as resource: header = resource.header - assert header == ["field1", "field2", "field3"] + assert header.field_names == ["field1", "field2", "field3"] assert header.labels == ["field1", "field2", "field3"] assert header.field_numbers == [1, 2, 3] assert header.row_numbers == [1] assert header.errors == [] - assert header == ["field1", "field2", "field3"] def test_extra_label(): schema = Schema(fields=[fields.AnyField(name="id")]) with TableResource(path="data/table.csv", schema=schema) as resource: header = resource.header - assert header == ["id"] + assert header.field_names == ["id"] assert header.labels == ["id", "name"] assert header.valid is False @@ -38,7 +37,7 @@ def test_missing_label(): ) with TableResource(path="data/table.csv", schema=schema) as resource: header = resource.header - assert header == ["id", "name", "extra"] + assert header.field_names == ["id", "name", "extra"] assert header.labels == ["id", "name"] assert header.valid is False @@ -314,3 +313,17 @@ def test_missing_primary_key_label_with_shema_sync_issue_1633( assert len(errors) == nb_errors for error, type_expected in zip(errors, types_errors_expected): assert error.type == type_expected + + +# Deprecated + + +# `Header` inherits from `List[str]` and, as such, exposes the schema field +# names — which `header.field_names` states explicitly. +def test_header_used_as_a_list_is_deprecated(): + with TableResource(data=[["field1", "field2"], [1, 2]]) as resource: + header = resource.header + assert header == ["field1", "field2"] + assert header[0] == "field1" + assert len(header) == 2 + assert list(header) == header.field_names diff --git a/frictionless/table/header.py b/frictionless/table/header.py index 9e59ce4dca..c06a70fdfd 100644 --- a/frictionless/table/header.py +++ b/frictionless/table/header.py @@ -46,7 +46,8 @@ def __init__( ignore_case: bool = False, fields_match: types.IFieldsMatch = "exact", ): - super().__init__(field.name for field in fields) + field_names = [field.name for field in fields] + super().__init__(field_names) self.__fields: List[Field] = [] for field in fields: copy = field.to_copy() @@ -55,7 +56,7 @@ def __init__( # primary_key" remain accurate. copy.schema = field.schema self.__fields.append(copy) - self.__field_names = self.copy() + self.__field_names = field_names self.__row_numbers = row_numbers self.__ignore_case = ignore_case self.__fields_match = fields_match @@ -268,8 +269,8 @@ def to_str(self): return helpers.stringify_csv_string(cells) def to_list(self): - """Convert to a list""" - return self.copy() + """Convert to a list of the schema field names""" + return list(self.__field_names) # Process From 6d5fc73d3c87261a0194e328531687b2a5559410 Mon Sep 17 00:00:00 2001 From: Pierre Camilleri Date: Mon, 27 Jul 2026 18:06:17 +0200 Subject: [PATCH 03/11] Deprecation warning for using Header as a list --- .../excel/parsers/__spec__/test_xlsx.py | 2 +- .../resources/__spec__/table/test_open.py | 2 +- frictionless/table/__spec__/test_header.py | 20 ++++++++--- frictionless/table/header.py | 35 ++++++++++++++++++- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/frictionless/formats/excel/parsers/__spec__/test_xlsx.py b/frictionless/formats/excel/parsers/__spec__/test_xlsx.py index 972126aa29..c481c74c1d 100644 --- a/frictionless/formats/excel/parsers/__spec__/test_xlsx.py +++ b/frictionless/formats/excel/parsers/__spec__/test_xlsx.py @@ -274,7 +274,7 @@ def test_xlsx_parser_multiline_header_with_merged_cells_issue_1024(): control = formats.ExcelControl(sheet="IPC", fill_merged_cells=True) dialect = Dialect(header_rows=[10, 11, 12], controls=[control]) with TableResource(path="data/issue-1024.xlsx", dialect=dialect) as resource: - assert resource.header + assert resource.header.field_names assert resource.header.field_names[21] == "Current Phase P3+ #" diff --git a/frictionless/resources/__spec__/table/test_open.py b/frictionless/resources/__spec__/table/test_open.py index 3e7e9daa46..621c0c27c5 100644 --- a/frictionless/resources/__spec__/table/test_open.py +++ b/frictionless/resources/__spec__/table/test_open.py @@ -36,7 +36,7 @@ def test_resource_open_read_rows(): with TableResource(path="data/table.csv") as resource: headers = resource.header row1, row2 = resource.read_rows() - assert headers == ["id", "name"] + assert headers.field_names == ["id", "name"] assert headers.field_numbers == [1, 2] assert headers.errors == [] assert headers.valid is True diff --git a/frictionless/table/__spec__/test_header.py b/frictionless/table/__spec__/test_header.py index 5ddd47faa9..0a500d697b 100644 --- a/frictionless/table/__spec__/test_header.py +++ b/frictionless/table/__spec__/test_header.py @@ -320,10 +320,20 @@ def test_missing_primary_key_label_with_shema_sync_issue_1633( # `Header` inherits from `List[str]` and, as such, exposes the schema field # names — which `header.field_names` states explicitly. -def test_header_used_as_a_list_is_deprecated(): +@pytest.mark.parametrize( + "usage, expected", + [ + (lambda header: header == ["field1", "field2"], True), + (lambda header: header[0], "field1"), + (lambda header: len(header), 2), + (lambda header: "field1" in header, True), + (lambda header: list(header), ["field1", "field2"]), + ], + ids=["==", "[]", "len()", "in", "iteration"], +) +def test_header_used_as_a_list_is_deprecated(usage, expected): with TableResource(data=[["field1", "field2"], [1, 2]]) as resource: header = resource.header - assert header == ["field1", "field2"] - assert header[0] == "field1" - assert len(header) == 2 - assert list(header) == header.field_names + with pytest.warns(DeprecationWarning, match="deprecated"): + assert usage(header) == expected + assert header.field_names == ["field1", "field2"] diff --git a/frictionless/table/header.py b/frictionless/table/header.py index c06a70fdfd..7b16051db8 100644 --- a/frictionless/table/header.py +++ b/frictionless/table/header.py @@ -1,7 +1,8 @@ from __future__ import annotations +import warnings from functools import cached_property -from typing import List, Optional, Tuple +from typing import Any, Iterator, List, Optional, Tuple from .. import errors, helpers, types from ..exception import FrictionlessException @@ -65,6 +66,38 @@ def __init__( self.__expected_fields: Optional[List[Field]] = None self.__process() + # Deprecated + + def __warn_list_usage(self, usage: str) -> None: + warnings.warn( + f"Using a Header as a list ({usage}) is deprecated, as the list " + "ambiguously holds the names of the schema fields. Use " + "`header.field_names` instead, or `header.labels` for the header " + "row as read from the data source.", + DeprecationWarning, + stacklevel=3, + ) + + def __eq__(self, other: Any) -> bool: + self.__warn_list_usage("==") + return super().__eq__(other) + + def __getitem__(self, index: Any) -> Any: + self.__warn_list_usage("[]") + return super().__getitem__(index) + + def __iter__(self) -> Iterator[str]: + self.__warn_list_usage("iteration") + return super().__iter__() + + def __len__(self) -> int: + self.__warn_list_usage("len()") + return super().__len__() + + def __contains__(self, item: Any) -> bool: + self.__warn_list_usage("in") + return super().__contains__(item) + @cached_property def labels(self): """ From 3a774a9dae2d096adf850882fe9e013fb720394d Mon Sep 17 00:00:00 2001 From: Pierre Camilleri Date: Tue, 28 Jul 2026 11:04:18 +0200 Subject: [PATCH 04/11] refactor: use LabelMatching for label<>field matching --- frictionless/table/header.py | 36 ++++------------- frictionless/table/label_matching.py | 58 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 frictionless/table/label_matching.py diff --git a/frictionless/table/header.py b/frictionless/table/header.py index 7b16051db8..6b0ba0f474 100644 --- a/frictionless/table/header.py +++ b/frictionless/table/header.py @@ -7,6 +7,7 @@ from .. import errors, helpers, types from ..exception import FrictionlessException from ..schema import Field +from .label_matching import LabelMatching # The `fieldsMatch` modes are told apart by which mismatch they tolerate: a # label with no matching field, or a declared field with no matching label @@ -25,7 +26,7 @@ class Header(List[str]): # type: ignore > Constructor of this object is not Public API > Deprecated: using a `Header` directly as a list is deprecated, as the list - > ambiguously holds the names of the *schema* fieldss. Use its properties instead: + > ambiguously holds the names of the *schema* fields. Use its properties instead: > `labels` for the header row as read from the data source, `fields`/`field_names` > for the schema fields. @@ -64,6 +65,7 @@ def __init__( self.__labels = labels self.__errors: List[errors.HeaderError] = [] self.__expected_fields: Optional[List[Field]] = None + self.__matching = LabelMatching(labels, self.__fields, ignore_case=ignore_case) self.__process() # Deprecated @@ -201,7 +203,7 @@ def get_expected_fields(self) -> List[Field]: expected: List[Field] = [] for label in self.__labels: - field = self.__find_field_by_name(label) + field = self.__matching.matching_field(label) if field is None: field = Field.from_descriptor({"name": label, "type": "any"}) expected.append(field) @@ -233,7 +235,7 @@ def _get_extra_labels(self) -> List[Tuple[int, str]]: return [ (number, label) for number, label in enumerate(labels, start=1) - if self.__find_field_by_name(label) is None + if self.__matching.matching_field(label) is None ] def _get_missing_fields(self) -> List[Tuple[int, Field]]: @@ -257,39 +259,19 @@ def _get_missing_fields(self) -> List[Tuple[int, Field]]: if not self.__matches_by_name: missing = fields[len(labels) :] if len(fields) > len(labels) else [] else: - normalized_labels = [self.__normalize(label) for label in labels] - - def is_absent(field: Field) -> bool: - return self.__normalize(field.name) not in normalized_labels def is_required(field: Field) -> bool: return field.required or ( field.schema is not None and field.name in field.schema.primary_key ) - missing = [field for field in fields if is_absent(field)] + missing = self.__matching.unmatched_fields if self.__fields_match in TOLERATES_MISSING_FIELDS: missing = [field for field in missing if is_required(field)] start = len(labels) + 1 return [(start + offset, field) for offset, field in enumerate(missing)] - def __has_matching_field(self) -> bool: - """Whether at least one label corresponds to a schema field""" - return any( - self.__find_field_by_name(label) is not None for label in self.__labels - ) - - def __find_field_by_name(self, name: str) -> Optional[Field]: - target = self.__normalize(name) - for f in self.__fields: - if self.__normalize(f.name) == target: - return f - return None - - def __normalize(self, s: str) -> str: - return s.lower() if self.__ignore_case else s - # Convert def to_str(self): @@ -330,11 +312,7 @@ def __process(self): ) # Unmatched header - if ( - self.__fields_match == "partial" - and fields - and not self.__has_matching_field() - ): + if self.__fields_match == "partial" and fields and not self.__matching.has_match: self.__errors.append( errors.UnmatchedHeaderError( note="", diff --git a/frictionless/table/label_matching.py b/frictionless/table/label_matching.py new file mode 100644 index 0000000000..26d849ea25 --- /dev/null +++ b/frictionless/table/label_matching.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from typing import Dict, List, Optional + +from ..schema import Field + + +class LabelMatching: + """Pairs the labels read from the data source with the schema fields, by name. + + Parameters: + labels (str[]): the header row as read from the data source + fields (Field[]): the fields declared in the schema, in schema order + ignore_case (bool): compare labels and field names case-insensitively + """ + + def __init__( + self, + labels: List[str], + fields: List[Field], + *, + ignore_case: bool = False, + ) -> None: + self.__labels = labels + self.__fields = fields + self.__ignore_case = ignore_case + + # Keyed by normalized name, in schema order; the first field wins in + # case of duplicates under normalization, so the duplicate fields are + # lost + fields_by_key: Dict[str, Field] = {} + for field in fields: + fields_by_key.setdefault(self.__normalize(field.name), field) + + self.__fields_by_key = fields_by_key + + def matching_field(self, label: str) -> Optional[Field]: + """Returns the field the given label matches, or None if there is none""" + return self.__fields_by_key.get(self.__normalize(label)) + + @property + def unmatched_fields(self) -> List[Field]: + """The fields no label matches, in schema order""" + matched = {self.__normalize(label) for label in self.__labels} + return [ + field + for field in self.__fields + if self.__normalize(field.name) not in matched + ] + + @property + def has_match(self) -> bool: + """Whether at least one label matches a schema field""" + return any(self.matching_field(label) is not None for label in self.__labels) + + def __normalize(self, name: str) -> str: + """The normalized value a label and a field name are compared through""" + return name.lower() if self.__ignore_case else name From cd58fb0f6dfae1d3e4cf42ccbea95a11a2607000 Mon Sep 17 00:00:00 2001 From: Pierre Camilleri Date: Tue, 28 Jul 2026 11:55:25 +0200 Subject: [PATCH 05/11] improve docs --- frictionless/table/header.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frictionless/table/header.py b/frictionless/table/header.py index 6b0ba0f474..dc5b2089b9 100644 --- a/frictionless/table/header.py +++ b/frictionless/table/header.py @@ -26,9 +26,9 @@ class Header(List[str]): # type: ignore > Constructor of this object is not Public API > Deprecated: using a `Header` directly as a list is deprecated, as the list - > ambiguously holds the names of the *schema* fields. Use its properties instead: - > `labels` for the header row as read from the data source, `fields`/`field_names` - > for the schema fields. + > ambiguously holds the names of the (possibly inferred) *schema* fields. Use its + > properties instead: `labels` for the header row as read from the data source, + > `fields`/`field_names` for the schema fields. Parameters: labels (any[]): the header row as read from the data source From ffbd23120c9a8851ee550cca3f18e7de7282960a Mon Sep 17 00:00:00 2001 From: Pierre Camilleri Date: Tue, 28 Jul 2026 12:16:45 +0200 Subject: [PATCH 06/11] Revert "Rm internal usage of Header as list" This reverts commit 01be157d70a56daea45ec931fdd8865ed6bb3685. --- .../formats/csv/__spec__/test_parser.py | 50 +++++++++---------- .../excel/parsers/__spec__/test_xls.py | 18 +++---- .../excel/parsers/__spec__/test_xlsx.py | 20 ++++---- .../formats/gsheets/__spec__/test_parser.py | 6 +-- .../formats/html/__spec__/test_parser.py | 6 +-- .../formats/inline/__spec__/test_parser.py | 14 +++--- .../json/parsers/__spec__/test_json.py | 14 +++--- .../json/parsers/__spec__/test_jsonl.py | 8 +-- .../formats/ods/__spec__/test_parser.py | 20 ++++---- .../formats/pandas/__spec__/test_parser.py | 2 +- .../formats/parquet/__spec__/test_parser.py | 10 ++-- .../formats/spss/__spec__/test_parser.py | 4 +- .../sql/__spec__/duckdb/test_parser.py | 16 +++--- .../formats/sql/__spec__/mysql/test_parser.py | 4 +- .../sql/__spec__/postgresql/test_parser.py | 4 +- .../formats/sql/__spec__/test_parser.py | 16 +++--- .../formats/yaml/__spec__/test_parser.py | 8 +-- .../package/__spec__/test_resources.py | 2 +- .../portals/ckan/__spec__/test_adapter.py | 2 +- .../resource/__spec__/test_validate.py | 2 +- .../__spec__/test_validate_dialect.py | 12 ++--- frictionless/resource/resource.py | 2 +- .../__spec__/table/test_compression.py | 28 +++++------ .../resources/__spec__/table/test_detector.py | 22 ++++---- .../resources/__spec__/table/test_dialect.py | 38 +++++++------- .../resources/__spec__/table/test_encoding.py | 6 +-- .../resources/__spec__/table/test_general.py | 11 ++-- .../__spec__/table/test_innerpath.py | 6 +-- .../resources/__spec__/table/test_open.py | 20 ++++---- .../resources/__spec__/table/test_schema.py | 4 +- .../resources/__spec__/table/test_write.py | 4 +- frictionless/resources/table.py | 2 +- .../schemes/aws/loaders/__spec__/test_s3.py | 6 +-- .../schemes/buffer/__spec__/test_loader.py | 2 +- .../schemes/local/__spec__/test_loader.py | 4 +- .../schemes/multipart/__spec__/test_loader.py | 4 +- .../schemes/remote/__spec__/test_loader.py | 4 +- .../schemes/stream/__spec__/test_loader.py | 4 +- frictionless/steps/table/table_normalize.py | 2 +- frictionless/system/__spec__/test_system.py | 2 +- frictionless/table/__spec__/test_header.py | 31 ++---------- frictionless/table/header.py | 9 ++-- 42 files changed, 211 insertions(+), 238 deletions(-) diff --git a/frictionless/formats/csv/__spec__/test_parser.py b/frictionless/formats/csv/__spec__/test_parser.py index 6e00091dc2..3a17427091 100644 --- a/frictionless/formats/csv/__spec__/test_parser.py +++ b/frictionless/formats/csv/__spec__/test_parser.py @@ -13,7 +13,7 @@ def test_csv_parser(): with TableResource(path="data/table.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -22,7 +22,7 @@ def test_csv_parser(): def test_csv_parser_with_bom(): with TableResource(path="data/bom.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -31,7 +31,7 @@ def test_csv_parser_with_bom(): def test_csv_parser_with_bom_with_encoding(): with TableResource(path="data/bom.csv", encoding="utf-8") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -41,7 +41,7 @@ def test_csv_parser_with_bom_with_encoding(): def test_csv_parser_excel(): data = b"header1,header2\nvalue1,value2\nvalue3,value4" with TableResource(data=data, format="csv") as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, @@ -52,7 +52,7 @@ def test_csv_parser_excel_tab(): data = b"header1\theader2\nvalue1\tvalue2\nvalue3\tvalue4" control = formats.CsvControl(delimiter="\t") with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, @@ -62,7 +62,7 @@ def test_csv_parser_excel_tab(): def test_csv_parser_unix(): data = b'"header1","header2"\n"value1","value2"\n"value3","value4"' with TableResource(data=data, format="csv") as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, @@ -72,7 +72,7 @@ def test_csv_parser_unix(): def test_csv_parser_escaping(): control = formats.CsvControl(escape_char="\\") with TableResource(path="data/escaping.csv", control=control) as resource: - assert resource.header.field_names == ["ID", "Test"] + assert resource.header == ["ID", "Test"] assert resource.read_rows() == [ {"ID": 1, "Test": "Test line 1"}, {"ID": 2, "Test": 'Test " line 2'}, @@ -82,7 +82,7 @@ def test_csv_parser_escaping(): def test_csv_parser_doublequote(): with TableResource(path="data/doublequote.csv") as resource: - assert len(resource.header.field_names) == 17 + assert len(resource.header) == 17 for row in resource.row_stream: assert len(row) == 17 @@ -90,7 +90,7 @@ def test_csv_parser_doublequote(): def test_csv_parser_stream(): data = open("data/table.csv", mode="rb") with TableResource(data=data, format="csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -100,7 +100,7 @@ def test_csv_parser_stream(): def test_csv_parser_buffer(): data = "id,name\n1,english\n2,中国人\n".encode("utf-8") with TableResource(data=data, format="csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -111,7 +111,7 @@ def test_csv_parser_buffer(): @pytest.mark.skipif(sys.version_info < (3, 10), reason="pytest-vcr bug in Python3.8/9") def test_csv_parser_remote(): with TableResource(path=BASEURL % "data/table.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -122,7 +122,7 @@ def test_csv_parser_remote(): def test_csv_parser_remote_non_ascii_url(): path = "http://data.defra.gov.uk/ops/government_procurement_card/over_£500_GPC_apr_2013.csv" with TableResource(path=path) as resource: - assert resource.header.field_names == [ + assert resource.header == [ "Entity", "Transaction Posting Date", "Merchant Name", @@ -135,7 +135,7 @@ def test_csv_parser_delimiter(): data = b'"header1";"header2"\n"value1";"value2"\n"value3";"value4"' control = formats.CsvControl(delimiter=";") with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, @@ -146,7 +146,7 @@ def test_csv_parser_escapechar(): data = b"header1%,header2\nvalue1%,value2\nvalue3%,value4" control = formats.CsvControl(escape_char="%") with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header.field_names == ["header1,header2"] + assert resource.header == ["header1,header2"] assert resource.read_rows() == [ {"header1,header2": "value1,value2"}, {"header1,header2": "value3,value4"}, @@ -157,7 +157,7 @@ def test_csv_parser_quotechar(): data = b"%header1,header2%\n%value1,value2%\n%value3,value4%" control = formats.CsvControl(quote_char="%") with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header.field_names == ["header1,header2"] + assert resource.header == ["header1,header2"] assert resource.read_rows() == [ {"header1,header2": "value1,value2"}, {"header1,header2": "value3,value4"}, @@ -168,7 +168,7 @@ def test_csv_parser_skipinitialspace(): data = b"header1, header2\nvalue1, value2\nvalue3, value4" control = formats.CsvControl(skip_initial_space=False) with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": " value2"}, {"header1": "value3", "header2": " value4"}, @@ -178,7 +178,7 @@ def test_csv_parser_skipinitialspace(): def test_csv_parser_skipinitialspace_default(): data = b"header1, header2\nvalue1, value2\nvalue3, value4" with TableResource(data=data, format="csv") as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, @@ -220,9 +220,9 @@ def test_csv_parser_dialect_should_not_persist_if_sniffing_fails_issue_goodtable data2 = b"a,b,c\n#comment" control = formats.CsvControl(delimiter=";") with TableResource(data=data1, format="csv", control=control) as resource: - assert resource.header.field_names == ["a", "b", "c"] + assert resource.header == ["a", "b", "c"] with TableResource(data=data2, format="csv") as resource: - assert resource.header.field_names == ["a", "b", "c"] + assert resource.header == ["a", "b", "c"] # TODO: recover on py3.11+ @@ -231,7 +231,7 @@ def test_csv_parser_quotechar_is_empty_string(): data = b'header1,header2",header3\nvalue1,value2",value3' control = formats.CsvControl(quote_char="") with TableResource(data=data, format="csv", control=control) as resource: - assert resource.header.field_names == ["header1", 'header2"', "header3"] + assert resource.header == ["header1", 'header2"', "header3"] assert resource.read_rows() == [ {"header1": "value1", 'header2"': 'value2"', "header3": "value3"}, ] @@ -241,7 +241,7 @@ def test_csv_parser_format_tsv(): detector = Detector(schema_patch={"missingValues": ["\\N"]}) with TableResource(path="data/table.tsv", detector=detector) as resource: assert resource.dialect.to_descriptor() == {"csv": {"delimiter": "\t"}} - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -258,7 +258,7 @@ def test_csv_parser_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.csv"))) source.write(target) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -272,7 +272,7 @@ def test_csv_parser_write_delimiter(tmpdir): target = TableResource(path=str(tmpdir.join("table.csv")), control=control) source.write(target) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.dialect.to_descriptor() == {"csv": {"delimiter": ";"}} assert target.read_rows() == [ {"id": 1, "name": "english"}, @@ -286,7 +286,7 @@ def test_csv_parser_write_inline_source(tmpdir): target = TableResource(path=str(tmpdir.join("table.csv"))) source.write(target) with target: - assert target.header.field_names == ["key1", "key2"] + assert target.header == ["key1", "key2"] assert target.read_rows() == [ {"key1": "value1", "key2": "value2"}, ] @@ -333,7 +333,7 @@ def test_csv_parser_write_skip_header(tmpdir): data = b"header1,header2\nvalue11,value12\nvalue21,value22" path = str(tmpdir.join("table.csv")) with TableResource(data=data, format="csv") as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] resource.write_table(path, dialect=Dialect(header=False)) with open(path, "rb") as file: assert file.read() == b"value11,value12\r\nvalue21,value22\r\n" diff --git a/frictionless/formats/excel/parsers/__spec__/test_xls.py b/frictionless/formats/excel/parsers/__spec__/test_xls.py index 36fd4f09e0..73e5a02213 100644 --- a/frictionless/formats/excel/parsers/__spec__/test_xls.py +++ b/frictionless/formats/excel/parsers/__spec__/test_xls.py @@ -11,7 +11,7 @@ def test_xls_parser(): with TableResource(path="data/table.xls") as table: - assert table.header.field_names == ["id", "name"] + assert table.header == ["id", "name"] assert table.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -21,7 +21,7 @@ def test_xls_parser(): @pytest.mark.vcr def test_xls_parser_remote(): with TableResource(path=BASEURL % "data/table.xls") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -32,7 +32,7 @@ def test_xls_parser_sheet_by_index(): path = "data/sheet2.xls" control = formats.ExcelControl(sheet=2) with TableResource(path=path, control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -51,7 +51,7 @@ def test_xls_parser_sheet_by_name(): path = "data/sheet2.xls" control = formats.ExcelControl(sheet="Sheet2") with TableResource(path=path, control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -91,7 +91,7 @@ def test_xls_parser_merged_cells_fill(): def test_xls_parser_with_boolean(): with TableResource(path="data/table-with-booleans.xls") as resource: - assert resource.header.field_names == ["id", "boolean"] + assert resource.header == ["id", "boolean"] assert resource.read_rows() == [ {"id": 1, "boolean": True}, {"id": 2, "boolean": False}, @@ -106,7 +106,7 @@ def test_xls_parser_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.xls"))) source.write(target) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -119,7 +119,7 @@ def test_xls_parser_write_sheet_name(tmpdir): target = TableResource(path=str(tmpdir.join("table.xls")), control=control) source.write(target) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -133,10 +133,10 @@ def test_xls_parser_write_skip_header(tmpdir): path = str(tmpdir.join("table.xls")) target = TableResource(path=path, dialect=dialect, control=control) with TableResource(data=data, format="csv") as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] resource.write_table(target) table = target.read_table() - assert table.header.field_names == ["field1", "field2"] + assert table.header == ["field1", "field2"] # Bugs diff --git a/frictionless/formats/excel/parsers/__spec__/test_xlsx.py b/frictionless/formats/excel/parsers/__spec__/test_xlsx.py index c481c74c1d..0f250c1dc5 100644 --- a/frictionless/formats/excel/parsers/__spec__/test_xlsx.py +++ b/frictionless/formats/excel/parsers/__spec__/test_xlsx.py @@ -23,7 +23,7 @@ def test_xlsx_parser_table(): data = io.open("data/table.xlsx", mode="rb") with TableResource(data=data, format="xlsx") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1.0, "name": "english"}, {"id": 2.0, "name": "中国人"}, @@ -38,7 +38,7 @@ def test_xlsx_parser_table(): def test_xlsx_parser_remote(): path = BASEURL % "data/table.xlsx" with TableResource(path=path) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1.0, "name": "english"}, {"id": 2.0, "name": "中国人"}, @@ -49,7 +49,7 @@ def test_xlsx_parser_sheet_by_index(): path = "data/sheet2.xlsx" control = formats.ExcelControl(sheet=2) with TableResource(path=path, control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1.0, "name": "english"}, {"id": 2.0, "name": "中国人"}, @@ -71,7 +71,7 @@ def test_xlsx_parser_sheet_by_name(): path = "data/sheet2.xlsx" control = formats.ExcelControl(sheet="Sheet2") with TableResource(path=path, control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1.0, "name": "english"}, {"id": 2.0, "name": "中国人"}, @@ -234,7 +234,7 @@ def test_xlsx_parser_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.xlsx"))) source.write(target) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -247,7 +247,7 @@ def test_xlsx_parser_write_sheet_name(tmpdir): target = TableResource(path=str(tmpdir.join("table.xlsx")), control=control) source.write(target) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -261,10 +261,10 @@ def test_xlsx_parser_write_skip_header(tmpdir): path = str(tmpdir.join("table.xlsx")) target = TableResource(path=path, dialect=dialect, control=control) with TableResource(data=data, format="csv") as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] resource.write_table(target) table = target.read_table() - assert table.header.field_names == ["field1", "field2"] + assert table.header == ["field1", "field2"] # Bugs @@ -274,8 +274,8 @@ def test_xlsx_parser_multiline_header_with_merged_cells_issue_1024(): control = formats.ExcelControl(sheet="IPC", fill_merged_cells=True) dialect = Dialect(header_rows=[10, 11, 12], controls=[control]) with TableResource(path="data/issue-1024.xlsx", dialect=dialect) as resource: - assert resource.header.field_names - assert resource.header.field_names[21] == "Current Phase P3+ #" + assert resource.header + assert resource.header[21] == "Current Phase P3+ #" def test_xlsx_parser_stats_no_bytes_and_hash_issue_938(): diff --git a/frictionless/formats/gsheets/__spec__/test_parser.py b/frictionless/formats/gsheets/__spec__/test_parser.py index 589830b496..815167fb45 100644 --- a/frictionless/formats/gsheets/__spec__/test_parser.py +++ b/frictionless/formats/gsheets/__spec__/test_parser.py @@ -15,7 +15,7 @@ def test_gsheets_parser(): path = "https://docs.google.com/spreadsheets/d/1mHIWnDvW9cALRMq9OdNfRwjAthCUFUOACPp0Lkyl7b4/edit?usp=sharing" with TableResource(path=path) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -26,7 +26,7 @@ def test_gsheets_parser(): def test_gsheets_parser_with_gid(): path = "https://docs.google.com/spreadsheets/d/1mHIWnDvW9cALRMq9OdNfRwjAthCUFUOACPp0Lkyl7b4/edit#gid=960698813" with TableResource(path=path) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 2, "name": "中国人"}, {"id": 3, "name": "german"}, @@ -54,7 +54,7 @@ def test_gsheets_parser_write(google_credentials_path): source = TableResource(path="data/table.csv") target = source.write(path=path, control=control) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/html/__spec__/test_parser.py b/frictionless/formats/html/__spec__/test_parser.py index 3dfb1367ae..225cd22180 100644 --- a/frictionless/formats/html/__spec__/test_parser.py +++ b/frictionless/formats/html/__spec__/test_parser.py @@ -18,7 +18,7 @@ def test_html_parser(path, selector): control = formats.HtmlControl(selector=selector) with TableResource(path=path, control=control) as resource: assert resource.format == "html" - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -34,7 +34,7 @@ def test_html_parser_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.html"))) source.write(target) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -48,7 +48,7 @@ def test_html_parser_newline_in_cell_issue_865(tmpdir): source = TableResource(path="data/table-with-newline.html") target = source.write(str(tmpdir.join("table.csv"))) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "with newline"}, {"id": 2, "name": "with newline"}, diff --git a/frictionless/formats/inline/__spec__/test_parser.py b/frictionless/formats/inline/__spec__/test_parser.py index 6a2b3da137..829c695b2a 100644 --- a/frictionless/formats/inline/__spec__/test_parser.py +++ b/frictionless/formats/inline/__spec__/test_parser.py @@ -12,7 +12,7 @@ def test_inline_parser(): data = [["id", "name"], ["1", "english"], ["2", "中国人"]] with TableResource(data=data) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -23,7 +23,7 @@ def test_inline_parser_keyed(): data = [{"id": "1", "name": "english"}, {"id": "2", "name": "中国人"}] with TableResource(data=data, format="inline") as resource: assert resource.dialect.to_descriptor() == {"inline": {"keyed": True}} - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -34,7 +34,7 @@ def test_inline_parser_keyed_order_is_preserved(): data = [{"name": "english", "id": "1"}, {"name": "中国人", "id": "2"}] with TableResource(data=data, format="inline") as resource: assert resource.dialect.to_descriptor() == {"inline": {"keyed": True}} - assert resource.header.field_names == ["name", "id"] + assert resource.header == ["name", "id"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -48,7 +48,7 @@ def test_inline_parser_keyed_with_keys_provided(): assert resource.dialect.to_descriptor() == { "inline": {"keyed": True, "keys": ["name", "id"]} } - assert resource.header.field_names == ["name", "id"] + assert resource.header == ["name", "id"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -62,7 +62,7 @@ def data(): yield ["2", "中国人"] with TableResource(data=data) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -76,7 +76,7 @@ def data(): yield ["2", "中国人"] with TableResource(data=data()) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -91,7 +91,7 @@ def test_inline_parser_from_ordered_dict(): with TableResource(data=data) as resource: rows = resource.read_rows() assert resource.dialect.to_descriptor() == {"inline": {"keyed": True}} - assert resource.header.field_names == ["name", "id"] + assert resource.header == ["name", "id"] assert rows[0].cells == ["english", "1"] assert rows[1].cells == ["中国人", "2"] diff --git a/frictionless/formats/json/parsers/__spec__/test_json.py b/frictionless/formats/json/parsers/__spec__/test_json.py index d64053f829..cf473ca415 100644 --- a/frictionless/formats/json/parsers/__spec__/test_json.py +++ b/frictionless/formats/json/parsers/__spec__/test_json.py @@ -15,7 +15,7 @@ def test_json_parser(): with TableResource(path="data/table.json") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -25,7 +25,7 @@ def test_json_parser(): def test_json_parser_keyed(): with TableResource(path="data/table.keyed.json") as resource: assert resource.dialect.to_descriptor() == {"json": {"keyed": True}} - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -38,7 +38,7 @@ def test_json_parser_keyed_with_keys_provided(): assert resource.dialect.to_descriptor() == { "json": {"keyed": True, "keys": ["name", "id"]} } - assert resource.header.field_names == ["name", "id"] + assert resource.header == ["name", "id"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -48,7 +48,7 @@ def test_json_parser_keyed_with_keys_provided(): def test_json_parser_from_buffer(): data = '[["id", "name"], [1, "english"], [2, "中国人"]]'.encode("utf-8") with TableResource(data=data, format="json") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -59,7 +59,7 @@ def test_json_parser_from_buffer_keyed(): data = '[{"id": 1, "name": "english" }, {"id": 2, "name": "中国人" }]'.encode("utf-8") with TableResource(data=data, format="json") as resource: assert resource.dialect.to_descriptor() == {"json": {"keyed": True}} - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -70,7 +70,7 @@ def test_json_parser_from_buffer_keyed(): @pytest.mark.skipif(sys.version_info < (3, 10), reason="pytest-vcr bug in Python3.8/9") def test_json_parser_from_remote(): with TableResource(path=BASEURL % "data/table.json") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -82,7 +82,7 @@ def test_json_parser_from_remote(): def test_json_parser_from_remote_keyed(): with TableResource(path=BASEURL % "data/table.keyed.json") as resource: assert resource.dialect.to_descriptor() == {"json": {"keyed": True}} - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/json/parsers/__spec__/test_jsonl.py b/frictionless/formats/json/parsers/__spec__/test_jsonl.py index 78cd82bb32..b29cb9339d 100644 --- a/frictionless/formats/json/parsers/__spec__/test_jsonl.py +++ b/frictionless/formats/json/parsers/__spec__/test_jsonl.py @@ -7,7 +7,7 @@ def test_jsonl_parser(): with TableResource(path="data/table.jsonl") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -16,7 +16,7 @@ def test_jsonl_parser(): def test_jsonl_parser_ndjson(): with TableResource(path="data/table.ndjson") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -30,7 +30,7 @@ def test_jsonl_parser_write(tmpdir): source = TableResource(path="data/table.csv") target = source.write(path=str(tmpdir.join("table.jsonl"))) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -42,7 +42,7 @@ def test_jsonl_parser_write_keyed(tmpdir): source = TableResource(path="data/table.csv") target = source.write(path=str(tmpdir.join("table.jsonl")), control=control) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/ods/__spec__/test_parser.py b/frictionless/formats/ods/__spec__/test_parser.py index ae0ec74be5..1ab6d564a8 100644 --- a/frictionless/formats/ods/__spec__/test_parser.py +++ b/frictionless/formats/ods/__spec__/test_parser.py @@ -15,7 +15,7 @@ def test_ods_parser(): with TableResource(path="data/table.ods") as resource: assert resource.format == "ods" - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -26,7 +26,7 @@ def test_ods_parser(): def test_ods_parser_remote(): path = BASEURL % "data/table.ods" with TableResource(path=path) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -36,7 +36,7 @@ def test_ods_parser_remote(): def test_ods_parser_sheet_by_index(): control = formats.OdsControl(sheet=1) with TableResource(path="data/table.ods", control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -48,7 +48,7 @@ def test_ods_parser_2nd_sheet_by_index(): with TableResource( path="data/table-with-two-sheets.ods", control=control ) as resource: - assert resource.header.field_names == ["a", "b", "c"] + assert resource.header == ["a", "b", "c"] assert resource.read_rows() == [{"a": 1, "b": 2, "c": 3}] @@ -65,7 +65,7 @@ def test_ods_parser_sheet_by_index_not_existent(): def test_ods_parser_sheet_by_name(): control = formats.OdsControl(sheet="Лист1") with TableResource(path="data/table.ods", control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -77,7 +77,7 @@ def test_ods_parser_2nd_sheet_by_name(): with TableResource( path="data/table-with-two-sheets.ods", control=control ) as resource: - assert resource.header.field_names == ["a", "b", "c"] + assert resource.header == ["a", "b", "c"] assert resource.read_rows() == [{"a": 1, "b": 2, "c": 3}] @@ -95,7 +95,7 @@ def test_ods_parser_sheet_by_name_not_existent(): def test_ods_parser_with_boolean(): with TableResource(path="data/table-with-booleans.ods") as resource: - assert resource.header.field_names == ["id", "boolean"] + assert resource.header == ["id", "boolean"] assert resource.read_rows() == [ {"id": 1, "boolean": True}, {"id": 2, "boolean": False}, @@ -121,7 +121,7 @@ def test_ods_parser_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.ods"))) source.write(target) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -135,7 +135,7 @@ def test_ods_parser_write_skip_header(tmpdir): path = str(tmpdir.join("table.ods")) target = TableResource(path=path, dialect=dialect, control=control) with TableResource(data=data, format="csv") as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] resource.write_table(target) table = target.read_table() - assert table.header.field_names == ["field1", "field2"] + assert table.header == ["field1", "field2"] diff --git a/frictionless/formats/pandas/__spec__/test_parser.py b/frictionless/formats/pandas/__spec__/test_parser.py index 2935026984..8ca453dd45 100644 --- a/frictionless/formats/pandas/__spec__/test_parser.py +++ b/frictionless/formats/pandas/__spec__/test_parser.py @@ -44,7 +44,7 @@ def test_pandas_parser(): dataframe = pd.DataFrame(data=tc["df_data"]) with TableResource(data=dataframe) as resource: - assert resource.header.field_names == tc["expected_header"], tc["name"] + assert resource.header == tc["expected_header"], tc["name"] assert resource.read_rows() == tc["expected_rows"], tc["name"] diff --git a/frictionless/formats/parquet/__spec__/test_parser.py b/frictionless/formats/parquet/__spec__/test_parser.py index f72af811f8..76b39efda0 100644 --- a/frictionless/formats/parquet/__spec__/test_parser.py +++ b/frictionless/formats/parquet/__spec__/test_parser.py @@ -10,7 +10,7 @@ def test_parquet_parser(): with TableResource(path="data/table.parq") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -19,7 +19,7 @@ def test_parquet_parser(): def test_parquet_parser_parquet_extension(): with TableResource(path="data/table.parquet") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -29,7 +29,7 @@ def test_parquet_parser_parquet_extension(): def test_parquet_parser_columns(): control = formats.ParquetControl(columns=["id"]) with TableResource(path="data/table.parq", control=control) as resource: - assert resource.header.field_names == ["id"] + assert resource.header == ["id"] assert resource.read_rows() == [ {"id": 1}, {"id": 2}, @@ -56,7 +56,7 @@ def test_parquet_parser_write(tmpdir): source.write(target) with target: assert target.format == "parq" - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -69,7 +69,7 @@ def test_parquet_parser_write_datetime_field_with_timezone(tmpdir): source.write(target) with target: assert target.format == "parq" - assert target.header.field_names == ["datetimewithtimezone"] + assert target.header == ["datetimewithtimezone"] assert target.read_rows() == [ { "datetimewithtimezone": datetime.datetime( diff --git a/frictionless/formats/spss/__spec__/test_parser.py b/frictionless/formats/spss/__spec__/test_parser.py index feffb62f92..3cc4c190b3 100644 --- a/frictionless/formats/spss/__spec__/test_parser.py +++ b/frictionless/formats/spss/__spec__/test_parser.py @@ -19,7 +19,7 @@ def test_spss_parser_write(tmpdir): source = TableResource(path="data/table.csv") target = source.write(str(tmpdir.join("table.sav"))) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -110,7 +110,7 @@ def test_spss_parser_write_timezone(tmpdir): source = TableResource(path="data/timezone.csv") target = source.write(path=str(tmpdir.join("table.sav"))) with target: - assert target.header.field_names == ["datetime", "time"] + assert target.header == ["datetime", "time"] assert target.read_rows() == [ { "datetime": datetime(2020, 1, 1, 15), diff --git a/frictionless/formats/sql/__spec__/duckdb/test_parser.py b/frictionless/formats/sql/__spec__/duckdb/test_parser.py index 3c78fc64fc..1393408e90 100644 --- a/frictionless/formats/sql/__spec__/duckdb/test_parser.py +++ b/frictionless/formats/sql/__spec__/duckdb/test_parser.py @@ -27,7 +27,7 @@ def test_sql_parser(duckdb_url_data): # https://github.com/Mause/duckdb_engine/blob/71b1ed2f63dc25a848995986401be765711d763d/duckdb_engine/__init__.py#L159 # "primaryKey": ["id"], } - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -37,7 +37,7 @@ def test_sql_parser(duckdb_url_data): def test_sql_parser_order_by(duckdb_url_data): control = formats.SqlControl(table="table", order_by="id") with TableResource(path=duckdb_url_data, control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -47,7 +47,7 @@ def test_sql_parser_order_by(duckdb_url_data): def test_sql_parser_order_by_desc(duckdb_url_data): control = formats.SqlControl(table="table", order_by="id desc") with TableResource(path=duckdb_url_data, control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 2, "name": "中国人"}, {"id": 1, "name": "english"}, @@ -57,7 +57,7 @@ def test_sql_parser_order_by_desc(duckdb_url_data): def test_sql_parser_where(duckdb_url_data): control = formats.SqlControl(table="table", where="name = '中国人'") with TableResource(path=duckdb_url_data, control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 2, "name": "中国人"}, ] @@ -93,7 +93,7 @@ def test_sql_parser_write(duckdb_url_data): control = formats.SqlControl(table="name", order_by="id") target = source.write(path=duckdb_url_data, control=control) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -105,7 +105,7 @@ def test_sql_parser_write_where(duckdb_url_data): control = formats.SqlControl(table="name", where="name = '中国人'") target = source.write(path=duckdb_url_data, control=control) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 2, "name": "中国人"}, ] @@ -116,7 +116,7 @@ def test_sql_parser_write_timezone(sqlite_url): control = formats.SqlControl(table="timezone") target = source.write(path=sqlite_url, control=control) with target: - assert target.header.field_names == ["datetime", "time"] + assert target.header == ["datetime", "time"] assert target.read_rows() == [ { "datetime": datetime(2020, 1, 1, 15), @@ -148,7 +148,7 @@ def test_sql_parser_write_string_pk_issue_777_sqlite(sqlite_url): target = source.write(path=sqlite_url, control=control) with target: assert target.schema.primary_key == ["name"] - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/sql/__spec__/mysql/test_parser.py b/frictionless/formats/sql/__spec__/mysql/test_parser.py index 966685f977..c95b61b2fb 100644 --- a/frictionless/formats/sql/__spec__/mysql/test_parser.py +++ b/frictionless/formats/sql/__spec__/mysql/test_parser.py @@ -15,7 +15,7 @@ def test_sql_parser_write_timezone_mysql(mysql_url): control = formats.SqlControl(table="timezone") target = source.write(path=mysql_url, control=control) with target: - assert target.header.field_names == ["datetime", "time"] + assert target.header == ["datetime", "time"] assert target.read_rows() == [ { "datetime": datetime(2020, 1, 1, 15), @@ -50,7 +50,7 @@ def test_sql_parser_write_string_pk_issue_777_mysql(mysql_url): target = source.write(path=mysql_url, control=control) with target: assert target.schema.primary_key == ["name"] - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/sql/__spec__/postgresql/test_parser.py b/frictionless/formats/sql/__spec__/postgresql/test_parser.py index 8aa0002e7e..6e8f7acc33 100644 --- a/frictionless/formats/sql/__spec__/postgresql/test_parser.py +++ b/frictionless/formats/sql/__spec__/postgresql/test_parser.py @@ -23,7 +23,7 @@ def test_sql_parser_write_timezone_postgresql(postgresql_url): control = formats.SqlControl(table="timezone") target = source.write(postgresql_url, control=control) with target: - assert target.header.field_names == ["datetime", "time"] + assert target.header == ["datetime", "time"] assert target.read_rows() == [ { "datetime": datetime(2020, 1, 1, 15), @@ -57,7 +57,7 @@ def test_sql_parser_write_string_pk_issue_777_postgresql(postgresql_url): target = source.write(postgresql_url, control=control) with target: assert target.schema.primary_key == ["name"] - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/sql/__spec__/test_parser.py b/frictionless/formats/sql/__spec__/test_parser.py index 6220640986..996fee9ffc 100644 --- a/frictionless/formats/sql/__spec__/test_parser.py +++ b/frictionless/formats/sql/__spec__/test_parser.py @@ -18,7 +18,7 @@ def test_sql_parser(sqlite_url_data): ], "primaryKey": ["id"], } - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -28,7 +28,7 @@ def test_sql_parser(sqlite_url_data): def test_sql_parser_order_by(sqlite_url_data): control = formats.SqlControl(table="table", order_by="id") with TableResource(path=sqlite_url_data, control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -38,7 +38,7 @@ def test_sql_parser_order_by(sqlite_url_data): def test_sql_parser_order_by_desc(sqlite_url_data): control = formats.SqlControl(table="table", order_by="id desc") with TableResource(path=sqlite_url_data, control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 2, "name": "中国人"}, {"id": 1, "name": "english"}, @@ -48,7 +48,7 @@ def test_sql_parser_order_by_desc(sqlite_url_data): def test_sql_parser_where(sqlite_url_data): control = formats.SqlControl(table="table", where="name = '中国人'") with TableResource(path=sqlite_url_data, control=control) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 2, "name": "中国人"}, ] @@ -84,7 +84,7 @@ def test_sql_parser_write(sqlite_url_data): control = formats.SqlControl(table="name", order_by="id") target = source.write(path=sqlite_url_data, control=control) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -96,7 +96,7 @@ def test_sql_parser_write_where(sqlite_url_data): control = formats.SqlControl(table="name", where="name = '中国人'") target = source.write(path=sqlite_url_data, control=control) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 2, "name": "中国人"}, ] @@ -107,7 +107,7 @@ def test_sql_parser_write_timezone(sqlite_url): control = formats.SqlControl(table="timezone") target = source.write(path=sqlite_url, control=control) with target: - assert target.header.field_names == ["datetime", "time"] + assert target.header == ["datetime", "time"] assert target.read_rows() == [ { "datetime": datetime(2020, 1, 1, 15), @@ -139,7 +139,7 @@ def test_sql_parser_write_string_pk_issue_777_sqlite(sqlite_url): target = source.write(path=sqlite_url, control=control) with target: assert target.schema.primary_key == ["name"] - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/formats/yaml/__spec__/test_parser.py b/frictionless/formats/yaml/__spec__/test_parser.py index 4c0c9d6129..186eab9423 100644 --- a/frictionless/formats/yaml/__spec__/test_parser.py +++ b/frictionless/formats/yaml/__spec__/test_parser.py @@ -6,7 +6,7 @@ def test_yaml_parser(): with TableResource(path="data/table.yaml") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -15,7 +15,7 @@ def test_yaml_parser(): def test_yaml_parser_keyed(): with TableResource(path="data/table.keyed.yaml") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -31,7 +31,7 @@ def test_yaml_parser_write(tmpdir): source.write(target) with target: assert target.format == "yaml" - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -42,7 +42,7 @@ def test_yaml_parser_write_skip_header(tmpdir): dialect = Dialect.from_descriptor({"header": False}) target = TableResource(path=str(tmpdir.join("table.yaml")), dialect=dialect) with TableResource(path="data/table.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] resource.write_table(target) assert target.read_rows() == [ {"field1": 1, "field2": "english"}, diff --git a/frictionless/package/__spec__/test_resources.py b/frictionless/package/__spec__/test_resources.py index 84dfda921e..037c2fba90 100644 --- a/frictionless/package/__spec__/test_resources.py +++ b/frictionless/package/__spec__/test_resources.py @@ -105,4 +105,4 @@ def test_package_resources_respect_layout_set_after_creation_issue_503(): resource = package.get_table_resource("table") resource.dialect = Dialect(comment_rows=[3]) assert resource.read_rows() == [{"id": 1, "name": "english"}] - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] diff --git a/frictionless/portals/ckan/__spec__/test_adapter.py b/frictionless/portals/ckan/__spec__/test_adapter.py index 57b5b7628f..95b955b2ad 100644 --- a/frictionless/portals/ckan/__spec__/test_adapter.py +++ b/frictionless/portals/ckan/__spec__/test_adapter.py @@ -556,7 +556,7 @@ def test_ckan_parser(options_lh): source = TableResource(path="data/table.csv") target = source.write(path=baseurl, control=control, format="csv") with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/resource/__spec__/test_validate.py b/frictionless/resource/__spec__/test_validate.py index 5e052dc4d9..bb111a52d8 100644 --- a/frictionless/resource/__spec__/test_validate.py +++ b/frictionless/resource/__spec__/test_validate.py @@ -632,7 +632,7 @@ def test_resource_validate_detector_infer_names(): assert resource.schema.fields[1].name == "name" assert resource.stats.rows == 3 assert resource.labels == [] - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] # Encoding diff --git a/frictionless/resource/__spec__/test_validate_dialect.py b/frictionless/resource/__spec__/test_validate_dialect.py index 6b252a6b84..984999b0c9 100644 --- a/frictionless/resource/__spec__/test_validate_dialect.py +++ b/frictionless/resource/__spec__/test_validate_dialect.py @@ -20,7 +20,7 @@ def test_resource_validate_dialect_header_false(): assert report.task.stats.get("rows") == 3 assert resource.dialect.header is False assert resource.labels == [] - assert resource.header.field_names == ["field1", "field2"] + assert resource.header == ["field1", "field2"] def test_resource_validate_dialect_none_extra_cell(): @@ -30,7 +30,7 @@ def test_resource_validate_dialect_none_extra_cell(): assert report.task.stats.get("rows") == 3 assert resource.dialect.header is False assert resource.labels == [] - assert resource.header.field_names == ["field1", "field2"] + assert resource.header == ["field1", "field2"] assert report.flatten(["rowNumber", "fieldNumber", "type"]) == [ [3, 3, "extra-cell"], ] @@ -40,7 +40,7 @@ def test_resource_validate_dialect_number(): dialect = Dialect(header_rows=[2]) resource = TableResource(path="data/matrix.csv", dialect=dialect) report = resource.validate() - assert resource.header.field_names == ["11", "12", "13", "14"] + assert resource.header == ["11", "12", "13", "14"] assert report.valid @@ -48,7 +48,7 @@ def test_resource_validate_dialect_list_of_numbers(): dialect = Dialect(header_rows=[2, 3, 4]) resource = TableResource(path="data/matrix.csv", dialect=dialect) report = resource.validate() - assert resource.header.field_names == ["11 21 31", "12 22 32", "13 23 33", "14 24 34"] + assert resource.header == ["11 21 31", "12 22 32", "13 23 33", "14 24 34"] assert report.valid @@ -56,7 +56,7 @@ def test_resource_validate_dialect_list_of_numbers_and_headers_join(): dialect = Dialect(header_rows=[2, 3, 4], header_join=".") resource = TableResource(path="data/matrix.csv", dialect=dialect) report = resource.validate() - assert resource.header.field_names == ["11.21.31", "12.22.32", "13.23.33", "14.24.34"] + assert resource.header == ["11.21.31", "12.22.32", "13.23.33", "14.24.34"] assert report.valid @@ -64,7 +64,7 @@ def test_resource_validate_dialect_skip_rows(): dialect = Dialect(comment_char="41", comment_rows=[2]) resource = TableResource(path="data/matrix.csv", dialect=dialect) report = resource.validate() - assert resource.header.field_names == ["f1", "f2", "f3", "f4"] + assert resource.header == ["f1", "f2", "f3", "f4"] assert report.task.stats.get("rows") == 2 assert report.task.valid diff --git a/frictionless/resource/resource.py b/frictionless/resource/resource.py index f9940617f9..dfe0fb410b 100644 --- a/frictionless/resource/resource.py +++ b/frictionless/resource/resource.py @@ -37,7 +37,7 @@ class Resource(Metadata, metaclass=Factory): # type: ignore ```python with Resource("data/table.csv") as resource: - resource.header.field_names == ["id", "name"] + resource.header == ["id", "name"] resource.read_rows() == [ {'id': 1, 'name': 'english'}, {'id': 2, 'name': '中国人'}, diff --git a/frictionless/resources/__spec__/table/test_compression.py b/frictionless/resources/__spec__/table/test_compression.py index 3b3bb5a2d7..8647483257 100644 --- a/frictionless/resources/__spec__/table/test_compression.py +++ b/frictionless/resources/__spec__/table/test_compression.py @@ -12,7 +12,7 @@ def test_resource_compression_local_csv_zip(): with TableResource(path="data/table.csv.zip") as resource: assert resource.innerpath == "table.csv" assert resource.compression == "zip" - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -23,7 +23,7 @@ def test_resource_compression_local_csv_zip_multiple_files(): with TableResource(path="data/table-multiple-files.zip", format="csv") as resource: assert resource.innerpath == "table-reverse.csv" assert resource.compression == "zip" - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "中国人"}, {"id": 2, "name": "english"}, @@ -35,7 +35,7 @@ def test_resource_compression_local_csv_zip_multiple_open(): # Open first time resource.open() - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -44,7 +44,7 @@ def test_resource_compression_local_csv_zip_multiple_open(): # Open second time resource.open() - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -56,7 +56,7 @@ def test_resource_compression_local_csv_gz(): with TableResource(path="data/table.csv.gz") as resource: assert resource.compression == "gz" assert resource.innerpath is None - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -67,7 +67,7 @@ def test_resource_compression_local_csv_xz(): with TableResource(path="data/table.csv.xz") as resource: assert resource.compression == "xz" assert resource.innerpath is None - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -78,7 +78,7 @@ def test_resource_compression_local_csv_bz2(): with TableResource(path="data/table.csv.bz2") as resource: assert resource.compression == "bz2" assert resource.innerpath is None - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -88,7 +88,7 @@ def test_resource_compression_local_csv_bz2(): def test_resource_compression_stream_csv_zip(): with open("data/table.csv.zip", "rb") as file: with TableResource(data=file, format="csv", compression="zip") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -98,7 +98,7 @@ def test_resource_compression_stream_csv_zip(): def test_resource_compression_stream_csv_gz(): with open("data/table.csv.gz", "rb") as file: with TableResource(data=file, format="csv", compression="gz") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -108,7 +108,7 @@ def test_resource_compression_stream_csv_gz(): def test_resource_compression_stream_csv_xz(): with open("data/table.csv.xz", "rb") as file: with TableResource(data=file, format="csv", compression="xz") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -118,7 +118,7 @@ def test_resource_compression_stream_csv_xz(): def test_resource_compression_stream_csv_bz2(): with open("data/table.csv.bz2", "rb") as file: with TableResource(data=file, format="csv", compression="bz2") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -129,7 +129,7 @@ def test_resource_compression_stream_csv_bz2(): def test_resource_compression_remote_csv_zip(): source = "https://raw.githubusercontent.com/frictionlessdata/tabulator-py/master/data/table.csv.zip" with TableResource(path=source) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -140,7 +140,7 @@ def test_resource_compression_remote_csv_zip(): def test_resource_compression_remote_csv_gz(): source = "https://raw.githubusercontent.com/frictionlessdata/tabulator-py/master/data/table.csv.gz" with TableResource(path=source) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -186,7 +186,7 @@ def test_resource_compression_legacy_no_value_framework_v4_issue_616(): with TableResource.from_descriptor(descriptor) as resource: assert resource.innerpath is None assert resource.compression is None - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/resources/__spec__/table/test_detector.py b/frictionless/resources/__spec__/table/test_detector.py index 2cbaf1e365..e3e2f250b4 100644 --- a/frictionless/resources/__spec__/table/test_detector.py +++ b/frictionless/resources/__spec__/table/test_detector.py @@ -10,7 +10,7 @@ def test_resource_detector_encoding_function(): assert resource.encoding == "utf-8" assert resource.sample == [["id", "name"], ["1", "english"], ["2", "中国人"]] assert resource.fragment == [["1", "english"], ["2", "中国人"]] - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] def test_resource_detector_field_type(): @@ -23,7 +23,7 @@ def test_resource_detector_field_type(): {"name": "name", "type": "string"}, ] } - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": "1", "name": "english"}, {"id": "2", "name": "中国人"}, @@ -41,7 +41,7 @@ def test_resource_detector_field_names(): ] } assert resource.labels == ["id", "name"] - assert resource.header.field_names == ["new1", "new2"] + assert resource.header == ["new1", "new2"] assert resource.read_rows() == [ {"new1": 1, "new2": "english"}, {"new1": 2, "new2": "中国人"}, @@ -58,7 +58,7 @@ def test_resource_detector_field_float_numbers(): {"name": "number", "type": "number", "floatNumber": True}, ] } - assert resource.header.field_names == ["number"] + assert resource.header == ["number"] assert resource.read_rows() == [ {"number": 1.1}, {"number": 2.2}, @@ -69,7 +69,7 @@ def test_resource_detector_field_float_numbers(): def test_resource_detector_field_type_with_open(): detector = Detector(field_type="string") with TableResource(path="data/table.csv", detector=detector) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.schema.to_descriptor() == { "fields": [ {"name": "id", "type": "string"}, @@ -92,7 +92,7 @@ def test_resource_detector_field_names_with_open(): ] } assert resource.labels == ["id", "name"] - assert resource.header.field_names == ["new1", "new2"] + assert resource.header == ["new1", "new2"] assert resource.read_rows() == [ {"new1": 1, "new2": "english"}, {"new1": 2, "new2": "中国人"}, @@ -115,7 +115,7 @@ def test_resource_detector_schema_sync(): assert resource.schema == schema assert resource.sample == [["name", "id"], ["english", "1"], ["中国人", "2"]] assert resource.fragment == [["english", "1"], ["中国人", "2"]] - assert resource.header.field_names == ["name", "id"] + assert resource.header == ["name", "id"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -139,7 +139,7 @@ def test_resource_detector_schema_sync_with_infer(): assert resource.schema == schema assert resource.sample == [["name", "id"], ["english", "1"], ["中国人", "2"]] assert resource.fragment == [["english", "1"], ["中国人", "2"]] - assert resource.header.field_names == ["name", "id"] + assert resource.header == ["name", "id"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -156,7 +156,7 @@ def test_resource_detector_schema_patch(): ] } assert resource.labels == ["id", "name"] - assert resource.header.field_names == ["ID", "name"] + assert resource.header == ["ID", "name"] assert resource.read_rows() == [ {"ID": "1", "name": "english"}, {"ID": "2", "name": "中国人"}, @@ -166,7 +166,7 @@ def test_resource_detector_schema_patch(): def test_resource_detector_schema_patch_missing_values(): detector = Detector(schema_patch={"missingValues": ["1", "2"]}) with TableResource(path="data/table.csv", detector=detector) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.schema.to_descriptor() == { "fields": [ {"name": "id", "type": "integer"}, @@ -191,7 +191,7 @@ def test_resource_detector_schema_patch_with_infer(): ] } assert resource.labels == ["id", "name"] - assert resource.header.field_names == ["ID", "name"] + assert resource.header == ["ID", "name"] assert resource.read_rows() == [ {"ID": "1", "name": "english"}, {"ID": "2", "name": "中国人"}, diff --git a/frictionless/resources/__spec__/table/test_dialect.py b/frictionless/resources/__spec__/table/test_dialect.py index c182a77a7b..002c1b26df 100644 --- a/frictionless/resources/__spec__/table/test_dialect.py +++ b/frictionless/resources/__spec__/table/test_dialect.py @@ -9,7 +9,7 @@ def test_resource_dialect_header(): with TableResource(path="data/table.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -36,7 +36,7 @@ def test_resource_dialect_header_false(): def test_resource_dialect_header_unicode(): with TableResource(path="data/table-unicode-headers.csv") as resource: - assert resource.header.field_names == ["id", "国人"] + assert resource.header == ["id", "国人"] assert resource.read_rows() == [ {"id": 1, "国人": "english"}, {"id": 2, "国人": "中国人"}, @@ -46,7 +46,7 @@ def test_resource_dialect_header_unicode(): def test_resource_dialect_header_stream_context_manager(): source = open("data/table.csv", mode="rb") with TableResource(data=source, format="csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -57,7 +57,7 @@ def test_resource_dialect_header_inline(): source = [[], ["id", "name"], ["1", "english"], ["2", "中国人"]] dialect = Dialect(header_rows=[2]) with TableResource(data=source, dialect=dialect) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -67,7 +67,7 @@ def test_resource_dialect_header_inline(): def test_resource_dialect_header_json_keyed(): data = "[" '{"id": 1, "name": "english"},' '{"id": 2, "name": "中国人"}]' with resources.TableResource(data=data.encode("utf-8"), format="json") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -77,7 +77,7 @@ def test_resource_dialect_header_json_keyed(): def test_resource_dialect_header_inline_keyed(): source = [{"id": "1", "name": "english"}, {"id": "2", "name": "中国人"}] with TableResource(data=source) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -89,7 +89,7 @@ def test_resource_dialect_header_inline_keyed_headers_is_none(): dialect = Dialect(header=False) with TableResource(data=source, dialect=dialect) as resource: assert resource.labels == [] - assert resource.header.field_names == ["field1", "field2"] + assert resource.header == ["field1", "field2"] assert resource.read_rows() == [ {"field1": "id", "field2": "name"}, {"field1": "1", "field2": "english"}, @@ -102,7 +102,7 @@ def test_resource_dialect_header_xlsx_multiline(): control = Control.from_descriptor({"type": "excel", "fillMergedCells": True}) dialect = Dialect(header_rows=[1, 2, 3, 4, 5], controls=[control]) with TableResource(path=source, dialect=dialect) as resource: - header = resource.header.field_names + header = resource.header assert header == [ "Region", "Caloric contribution (%)", @@ -118,7 +118,7 @@ def test_resource_dialect_header_csv_multiline_headers_join(): source = b"k1\nk2\nv1\nv2\nv3" dialect = Dialect(header_rows=[1, 2], header_join=":") with TableResource(data=source, format="csv", dialect=dialect) as resource: - assert resource.header.field_names == ["k1:k2"] + assert resource.header == ["k1:k2"] assert resource.read_rows() == [ {"k1:k2": "v1"}, {"k1:k2": "v2"}, @@ -130,7 +130,7 @@ def test_resource_dialect_header_csv_multiline_headers_duplicates(): source = b"k1\nk1\nv1\nv2\nv3" dialect = Dialect(header_rows=[1, 2]) with TableResource(data=source, format="csv", dialect=dialect) as resource: - assert resource.header.field_names == ["k1"] + assert resource.header == ["k1"] assert resource.read_rows() == [ {"k1": "v1"}, {"k1": "v2"}, @@ -143,7 +143,7 @@ def test_resource_dialect_header_strip_and_non_strings(): dialect = Dialect(header_rows=[1]) with TableResource(data=source, dialect=dialect) as resource: assert resource.labels == ["header", "2", "3", ""] - assert resource.header.field_names == ["header", "2", "3", "field4"] + assert resource.header == ["header", "2", "3", "field4"] assert resource.read_rows() == [ {"header": "value1", "2": "value2", "3": "value3", "field4": "value4"}, ] @@ -154,7 +154,7 @@ def test_resource_layout_header_case_default(): with TableResource(path="data/table.csv", schema=schema) as resource: assert resource.schema.field_names == ["ID", "NAME"] assert resource.labels == ["id", "name"] - assert resource.header.field_names == ["ID", "NAME"] + assert resource.header == ["ID", "NAME"] assert resource.header.valid is False assert resource.header.errors[0].type == "incorrect-label" assert resource.header.errors[1].type == "incorrect-label" @@ -166,7 +166,7 @@ def test_resource_layout_header_case_is_false(): with TableResource(path="data/table.csv", dialect=dialect, schema=schema) as resource: assert resource.schema.field_names == ["ID", "NAME"] assert resource.labels == ["id", "name"] - assert resource.header.field_names == ["ID", "NAME"] + assert resource.header == ["ID", "NAME"] assert resource.header.valid is True @@ -174,7 +174,7 @@ def test_resource_dialect_skip_rows(): source = "data/skip-rows.csv" dialect = Dialect(comment_char="#", comment_rows=[5]) with TableResource(path=source, dialect=dialect) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, ] @@ -184,7 +184,7 @@ def test_resource_dialect_skip_rows_with_headers(): source = "data/skip-rows.csv" dialect = Dialect(comment_char="#") with TableResource(path=source, dialect=dialect) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -195,7 +195,7 @@ def test_resource_layout_skip_rows_with_headers_example_from_readme(): dialect = Dialect(comment_char="#") source = [["#comment"], ["name", "order"], ["John", 1], ["Alex", 2]] with TableResource(data=source, dialect=dialect) as resource: - assert resource.header.field_names == ["name", "order"] + assert resource.header == ["name", "order"] assert resource.read_rows() == [ {"name": "John", "order": 1}, {"name": "Alex", "order": 2}, @@ -233,7 +233,7 @@ def test_resource_dialect_csv_default(): assert control.double_quote is True assert control.quote_char == '"' assert control.skip_initial_space is False - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.dialect.header is True assert resource.dialect.header_rows == [1] # TODO: review @@ -248,7 +248,7 @@ def test_resource_dialect_csv_default(): def test_resource_dialect_csv_delimiter(): with TableResource(path="data/delimiter.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.dialect.to_descriptor() == {"csv": {"delimiter": ";"}} assert resource.read_rows() == [ {"id": 1, "name": "english"}, @@ -260,7 +260,7 @@ def test_resource_dialect_json_property(): data = b'{"root": [["header1", "header2"], ["value1", "value2"]]}' dialect = Dialect.from_descriptor({"json": {"property": "root"}}) with resources.TableResource(data=data, format="json", dialect=dialect) as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, ] diff --git a/frictionless/resources/__spec__/table/test_encoding.py b/frictionless/resources/__spec__/table/test_encoding.py index 8e6dc35df9..f0e75455fe 100644 --- a/frictionless/resources/__spec__/table/test_encoding.py +++ b/frictionless/resources/__spec__/table/test_encoding.py @@ -9,7 +9,7 @@ def test_resource_encoding(): with TableResource(path="data/table.csv") as resource: assert resource.encoding == "utf-8" - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -19,7 +19,7 @@ def test_resource_encoding(): def test_resource_encoding_explicit_utf8(): with TableResource(path="data/table.csv", encoding="utf-8") as resource: assert resource.encoding == "utf-8" - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -29,7 +29,7 @@ def test_resource_encoding_explicit_utf8(): def test_resource_encoding_explicit_latin1(): with TableResource(path="data/latin1.csv", encoding="latin1") as resource: assert resource.encoding == "iso8859-1" - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "©"}, diff --git a/frictionless/resources/__spec__/table/test_general.py b/frictionless/resources/__spec__/table/test_general.py index 29771482bc..3458988a62 100644 --- a/frictionless/resources/__spec__/table/test_general.py +++ b/frictionless/resources/__spec__/table/test_general.py @@ -127,7 +127,7 @@ def test_resource_source_path(): assert resource.sample == [["id", "name"], ["1", "english"], ["2", "中国人"]] assert resource.fragment == [["1", "english"], ["2", "中国人"]] assert resource.labels == ["id", "name"] - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] if not platform.type == "windows": assert resource.stats.md5 == "6c2c61dd9b0e9c6876139a449ed87933" assert ( @@ -193,7 +193,7 @@ def test_resource_source_data(): assert resource.sample == data assert resource.fragment == data[1:] assert resource.labels == ["id", "name"] - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.stats.fields == 2 assert resource.stats.rows == 2 @@ -241,7 +241,7 @@ def test_resource_skip_blank_at_the_end_issue_bco_dmo_33(): source = "data/skip-blank-at-the-end.csv" with TableResource(path=source, dialect=dialect) as resource: rows = resource.read_rows() - assert resource.header.field_names == ["test1", "test2"] + assert resource.header == ["test1", "test2"] assert rows[0].cells == ["1", "2"] assert rows[1].cells == [] @@ -271,10 +271,7 @@ def test_resource_skip_rows_non_string_cell_issue_320(): controls=[Control.from_descriptor({"type": "excel", "fillMergedCells": True})], ) with TableResource(path=source, dialect=dialect) as resource: - assert ( - resource.header.field_names[7] - == "Current Population Analysed % of total county Pop" - ) + assert resource.header[7] == "Current Population Analysed % of total county Pop" @pytest.mark.skipif(platform.type == "windows", reason="Fix on Windows") diff --git a/frictionless/resources/__spec__/table/test_innerpath.py b/frictionless/resources/__spec__/table/test_innerpath.py index 0abd5f0f63..722a072b2e 100644 --- a/frictionless/resources/__spec__/table/test_innerpath.py +++ b/frictionless/resources/__spec__/table/test_innerpath.py @@ -8,7 +8,7 @@ def test_resource_innerpath_local_csv_zip(): assert resource.place == "data/table.csv.zip -> table.csv" assert resource.compression == "zip" assert resource.innerpath == "table.csv" - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -20,7 +20,7 @@ def test_resource_innerpath_local_csv_zip_multiple_files(): assert resource.place == "data/table-multiple-files.zip -> table-reverse.csv" assert resource.compression == "zip" assert resource.innerpath == "table-reverse.csv" - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "中国人"}, {"id": 2, "name": "english"}, @@ -34,7 +34,7 @@ def test_resource_innerpath_local_csv_zip_multiple_files_explicit(): assert resource.place == "data/table-multiple-files.zip -> table.csv" assert resource.compression == "zip" assert resource.innerpath == "table.csv" - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/resources/__spec__/table/test_open.py b/frictionless/resources/__spec__/table/test_open.py index 621c0c27c5..96829c5525 100644 --- a/frictionless/resources/__spec__/table/test_open.py +++ b/frictionless/resources/__spec__/table/test_open.py @@ -18,7 +18,7 @@ def test_resource_open(): assert resource.compression is None assert resource.sample == [["id", "name"], ["1", "english"], ["2", "中国人"]] assert resource.fragment == [["1", "english"], ["2", "中国人"]] - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.header.row_numbers == [1] assert resource.schema.to_descriptor() == { "fields": [ @@ -54,7 +54,7 @@ def test_resource_open_read_rows(): def test_resource_open_row_stream(): with TableResource(path="data/table.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert list(resource.row_stream) == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -64,7 +64,7 @@ def test_resource_open_row_stream(): def test_resource_open_row_stream_iterate(): with TableResource(path="data/table.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] for row in resource.row_stream: assert len(row) == 2 assert row.row_number in [2, 3] @@ -78,7 +78,7 @@ def test_resource_open_row_stream_error_cells(): detector = Detector(field_type="integer") with TableResource(path="data/table.csv", detector=detector) as resource: row1, row2 = resource.read_rows() - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert row1.errors[0].type == "type-error" assert row1.error_cells == {"name": "english"} assert row1.to_dict() == {"id": 1, "name": None} @@ -93,7 +93,7 @@ def test_resource_open_row_stream_blank_cells(): detector = Detector(schema_patch={"missingValues": ["1", "2"]}) with TableResource(path="data/table.csv", detector=detector) as resource: row1, row2 = resource.read_rows() - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert row1.blank_cells == {"id": "1"} assert row1.to_dict() == {"id": None, "name": "english"} assert row1.valid is True @@ -136,14 +136,14 @@ def test_resource_open_cell_stream_iterate(): def test_resource_open_empty(): with TableResource(path="data/empty.csv") as resource: assert resource.header.missing - assert resource.header.field_names == [] + assert resource.header == [] assert resource.schema.to_descriptor() == {"fields": []} assert resource.read_rows() == [] def test_resource_open_without_rows(): with TableResource(path="data/without-rows.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [] assert resource.schema.to_descriptor() == { "fields": [ @@ -158,7 +158,7 @@ def test_resource_open_without_headers(): with TableResource(path="data/without-headers.csv", dialect=dialect) as resource: assert resource.labels == [] assert resource.header.missing - assert resource.header.field_names == ["field1", "field2"] + assert resource.header == ["field1", "field2"] assert resource.schema.to_descriptor() == { "fields": [ {"name": "field1", "type": "integer"}, @@ -184,7 +184,7 @@ def test_resource_open_source_error_data(): def test_resource_reopen(): with TableResource(path="data/table.csv") as resource: # Open - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -192,7 +192,7 @@ def test_resource_reopen(): # Re-open resource.open() - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/resources/__spec__/table/test_schema.py b/frictionless/resources/__spec__/table/test_schema.py index c198c1be84..a7883d1d19 100644 --- a/frictionless/resources/__spec__/table/test_schema.py +++ b/frictionless/resources/__spec__/table/test_schema.py @@ -112,7 +112,7 @@ def test_resource_schema_from_path_error_bad_path(): def test_resource_schema_inferred(): with TableResource(path="data/table.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.schema.to_descriptor() == { "fields": [ {"name": "id", "type": "integer"}, @@ -136,7 +136,7 @@ def test_resource_schema_provided(): ) with TableResource(path="data/table.csv", schema=schema) as resource: assert resource.labels == ["id", "name"] - assert resource.header.field_names == ["new1", "new2"] + assert resource.header == ["new1", "new2"] assert resource.schema.to_descriptor() == { "fields": [ {"name": "new1", "type": "string"}, diff --git a/frictionless/resources/__spec__/table/test_write.py b/frictionless/resources/__spec__/table/test_write.py index bac95fb058..6938f03265 100644 --- a/frictionless/resources/__spec__/table/test_write.py +++ b/frictionless/resources/__spec__/table/test_write.py @@ -12,7 +12,7 @@ def test_resource_write(tmpdir): target = TableResource(path=str(tmpdir.join("table.csv"))) source.write(target) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -24,7 +24,7 @@ def test_resource_write_to_path(tmpdir): source = TableResource(path="data/table.csv") target = source.write(str(tmpdir.join("table.csv"))) with target: - assert target.header.field_names == ["id", "name"] + assert target.header == ["id", "name"] assert target.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/resources/table.py b/frictionless/resources/table.py index 049909e106..306b360767 100644 --- a/frictionless/resources/table.py +++ b/frictionless/resources/table.py @@ -629,7 +629,7 @@ def to_snap(self, *, json: bool = False): """ snap: List[List[Any]] = [] with helpers.ensure_open(self): - snap.append(list(self.header.field_names)) + snap.append(self.header.to_list()) for row in self.row_stream: snap.append(row.to_list(json=json)) return snap diff --git a/frictionless/schemes/aws/loaders/__spec__/test_s3.py b/frictionless/schemes/aws/loaders/__spec__/test_s3.py index dcb29affac..347bf3d0fe 100644 --- a/frictionless/schemes/aws/loaders/__spec__/test_s3.py +++ b/frictionless/schemes/aws/loaders/__spec__/test_s3.py @@ -26,7 +26,7 @@ def test_s3_loader(bucket_name): # Read with TableResource(path="s3://%s/table.csv" % bucket_name) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -48,7 +48,7 @@ def test_s3_loader_write(bucket_name): # Read with TableResource(path="s3://%s/table.csv" % bucket_name) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -132,7 +132,7 @@ def test_s3_loader_problem_with_spaces_issue_501(bucket_name): # Read with TableResource(path="s3://%s/table with space.csv" % bucket_name) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/schemes/buffer/__spec__/test_loader.py b/frictionless/schemes/buffer/__spec__/test_loader.py index 807c1c17f9..85e1f9c5cb 100644 --- a/frictionless/schemes/buffer/__spec__/test_loader.py +++ b/frictionless/schemes/buffer/__spec__/test_loader.py @@ -9,7 +9,7 @@ def test_buffer_loader(): data = b"header1,header2\nvalue1,value2\nvalue3,value4" with TableResource(data=data, format="csv") as resource: - assert resource.header.field_names == ["header1", "header2"] + assert resource.header == ["header1", "header2"] assert resource.read_rows() == [ {"header1": "value1", "header2": "value2"}, {"header1": "value3", "header2": "value4"}, diff --git a/frictionless/schemes/local/__spec__/test_loader.py b/frictionless/schemes/local/__spec__/test_loader.py index 2a9a032983..944ea2727e 100644 --- a/frictionless/schemes/local/__spec__/test_loader.py +++ b/frictionless/schemes/local/__spec__/test_loader.py @@ -8,7 +8,7 @@ def test_local_loader(): with TableResource(path="data/table.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -19,7 +19,7 @@ def test_local_loader_pathlib_path(): pathlib = import_module("pathlib") with Resource(pathlib.Path("data/table.csv")) as resource: assert isinstance(resource, TableResource) - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/schemes/multipart/__spec__/test_loader.py b/frictionless/schemes/multipart/__spec__/test_loader.py index f3fb21d247..e7aac1abfa 100644 --- a/frictionless/schemes/multipart/__spec__/test_loader.py +++ b/frictionless/schemes/multipart/__spec__/test_loader.py @@ -16,7 +16,7 @@ def test_multipart_loader(): with TableResource( path="data/chunk1.csv", extrapaths=["data/chunk2.csv"] ) as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -170,7 +170,7 @@ def test_multipart_loader_with_compressed_parts_issue_1215(): ) as resource: assert resource.innerpath is None assert resource.compression == "zip" - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/schemes/remote/__spec__/test_loader.py b/frictionless/schemes/remote/__spec__/test_loader.py index eeedeeb07f..7c4800b3a3 100644 --- a/frictionless/schemes/remote/__spec__/test_loader.py +++ b/frictionless/schemes/remote/__spec__/test_loader.py @@ -15,7 +15,7 @@ @pytest.mark.skipif(sys.version_info < (3, 10), reason="pytest-vcr bug in Python3.8/9") def test_remote_loader(): with TableResource(path=BASEURL % "data/table.csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -57,7 +57,7 @@ def test_remote_loader_http_preload(): assert control.http_preload is True assert resource.sample == [["id", "name"], ["1", "english"], ["2", "中国人"]] assert resource.fragment == [["1", "english"], ["2", "中国人"]] - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] # Write diff --git a/frictionless/schemes/stream/__spec__/test_loader.py b/frictionless/schemes/stream/__spec__/test_loader.py index 931dcaeb39..291367a28f 100644 --- a/frictionless/schemes/stream/__spec__/test_loader.py +++ b/frictionless/schemes/stream/__spec__/test_loader.py @@ -9,7 +9,7 @@ def test_stream_loader(): with open("data/table.csv", mode="rb") as file: with TableResource(data=file, format="csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, @@ -19,7 +19,7 @@ def test_stream_loader(): def test_stream_loader_text_stream(): with open("data/table.csv") as file: with TableResource(data=file, format="csv") as resource: - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert resource.read_rows() == [ {"id": 1, "name": "english"}, {"id": 2, "name": "中国人"}, diff --git a/frictionless/steps/table/table_normalize.py b/frictionless/steps/table/table_normalize.py index 1943770db1..409d2a90ab 100644 --- a/frictionless/steps/table/table_normalize.py +++ b/frictionless/steps/table/table_normalize.py @@ -29,7 +29,7 @@ def transform_resource(self, resource: Resource): # Data def data(): # type: ignore with current: - yield list(current.header.field_names) # type: ignore + yield current.header.to_list() # type: ignore for row in current.row_stream: # type: ignore yield row.to_list() # type: ignore diff --git a/frictionless/system/__spec__/test_system.py b/frictionless/system/__spec__/test_system.py index a2199e6bfe..3f6d7a9d59 100644 --- a/frictionless/system/__spec__/test_system.py +++ b/frictionless/system/__spec__/test_system.py @@ -21,5 +21,5 @@ def test_system_use_context_http_session(): with TableResource(path=BASEURL % "data/table.csv") as resource: control = resource.dialect.get_control("remote") assert isinstance(control, schemes.RemoteControl) - assert resource.header.field_names == ["id", "name"] + assert resource.header == ["id", "name"] assert system.http_session is not session diff --git a/frictionless/table/__spec__/test_header.py b/frictionless/table/__spec__/test_header.py index 0a500d697b..4afb6e73a1 100644 --- a/frictionless/table/__spec__/test_header.py +++ b/frictionless/table/__spec__/test_header.py @@ -11,18 +11,19 @@ def test_basic(): with TableResource(data=[["field1", "field2", "field3"], [1, 2, 3]]) as resource: header = resource.header - assert header.field_names == ["field1", "field2", "field3"] + assert header == ["field1", "field2", "field3"] assert header.labels == ["field1", "field2", "field3"] assert header.field_numbers == [1, 2, 3] assert header.row_numbers == [1] assert header.errors == [] + assert header == ["field1", "field2", "field3"] def test_extra_label(): schema = Schema(fields=[fields.AnyField(name="id")]) with TableResource(path="data/table.csv", schema=schema) as resource: header = resource.header - assert header.field_names == ["id"] + assert header == ["id"] assert header.labels == ["id", "name"] assert header.valid is False @@ -37,7 +38,7 @@ def test_missing_label(): ) with TableResource(path="data/table.csv", schema=schema) as resource: header = resource.header - assert header.field_names == ["id", "name", "extra"] + assert header == ["id", "name", "extra"] assert header.labels == ["id", "name"] assert header.valid is False @@ -313,27 +314,3 @@ def test_missing_primary_key_label_with_shema_sync_issue_1633( assert len(errors) == nb_errors for error, type_expected in zip(errors, types_errors_expected): assert error.type == type_expected - - -# Deprecated - - -# `Header` inherits from `List[str]` and, as such, exposes the schema field -# names — which `header.field_names` states explicitly. -@pytest.mark.parametrize( - "usage, expected", - [ - (lambda header: header == ["field1", "field2"], True), - (lambda header: header[0], "field1"), - (lambda header: len(header), 2), - (lambda header: "field1" in header, True), - (lambda header: list(header), ["field1", "field2"]), - ], - ids=["==", "[]", "len()", "in", "iteration"], -) -def test_header_used_as_a_list_is_deprecated(usage, expected): - with TableResource(data=[["field1", "field2"], [1, 2]]) as resource: - header = resource.header - with pytest.warns(DeprecationWarning, match="deprecated"): - assert usage(header) == expected - assert header.field_names == ["field1", "field2"] diff --git a/frictionless/table/header.py b/frictionless/table/header.py index dc5b2089b9..6cd80156d6 100644 --- a/frictionless/table/header.py +++ b/frictionless/table/header.py @@ -48,8 +48,7 @@ def __init__( ignore_case: bool = False, fields_match: types.IFieldsMatch = "exact", ): - field_names = [field.name for field in fields] - super().__init__(field_names) + super().__init__(field.name for field in fields) self.__fields: List[Field] = [] for field in fields: copy = field.to_copy() @@ -58,7 +57,7 @@ def __init__( # primary_key" remain accurate. copy.schema = field.schema self.__fields.append(copy) - self.__field_names = field_names + self.__field_names = self.copy() self.__row_numbers = row_numbers self.__ignore_case = ignore_case self.__fields_match = fields_match @@ -284,8 +283,8 @@ def to_str(self): return helpers.stringify_csv_string(cells) def to_list(self): - """Convert to a list of the schema field names""" - return list(self.__field_names) + """Convert to a list""" + return self.copy() # Process From 3c05afd7e89f763239f0565f10961e48ff6ab626 Mon Sep 17 00:00:00 2001 From: Pierre Camilleri Date: Tue, 28 Jul 2026 12:17:43 +0200 Subject: [PATCH 07/11] Revert "Deprecation warning for using Header as a list" This reverts commit 6d5fc73d3c87261a0194e328531687b2a5559410. --- .../resources/__spec__/table/test_open.py | 2 +- frictionless/table/header.py | 35 +------------------ 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/frictionless/resources/__spec__/table/test_open.py b/frictionless/resources/__spec__/table/test_open.py index 96829c5525..a2968d924f 100644 --- a/frictionless/resources/__spec__/table/test_open.py +++ b/frictionless/resources/__spec__/table/test_open.py @@ -36,7 +36,7 @@ def test_resource_open_read_rows(): with TableResource(path="data/table.csv") as resource: headers = resource.header row1, row2 = resource.read_rows() - assert headers.field_names == ["id", "name"] + assert headers == ["id", "name"] assert headers.field_numbers == [1, 2] assert headers.errors == [] assert headers.valid is True diff --git a/frictionless/table/header.py b/frictionless/table/header.py index 6cd80156d6..b9459133d2 100644 --- a/frictionless/table/header.py +++ b/frictionless/table/header.py @@ -1,8 +1,7 @@ from __future__ import annotations -import warnings from functools import cached_property -from typing import Any, Iterator, List, Optional, Tuple +from typing import List, Optional, Tuple from .. import errors, helpers, types from ..exception import FrictionlessException @@ -67,38 +66,6 @@ def __init__( self.__matching = LabelMatching(labels, self.__fields, ignore_case=ignore_case) self.__process() - # Deprecated - - def __warn_list_usage(self, usage: str) -> None: - warnings.warn( - f"Using a Header as a list ({usage}) is deprecated, as the list " - "ambiguously holds the names of the schema fields. Use " - "`header.field_names` instead, or `header.labels` for the header " - "row as read from the data source.", - DeprecationWarning, - stacklevel=3, - ) - - def __eq__(self, other: Any) -> bool: - self.__warn_list_usage("==") - return super().__eq__(other) - - def __getitem__(self, index: Any) -> Any: - self.__warn_list_usage("[]") - return super().__getitem__(index) - - def __iter__(self) -> Iterator[str]: - self.__warn_list_usage("iteration") - return super().__iter__() - - def __len__(self) -> int: - self.__warn_list_usage("len()") - return super().__len__() - - def __contains__(self, item: Any) -> bool: - self.__warn_list_usage("in") - return super().__contains__(item) - @cached_property def labels(self): """ From 95c45913f2aa700cc3d386c62d655e9f68e255c1 Mon Sep 17 00:00:00 2001 From: Pierre Camilleri Date: Tue, 28 Jul 2026 12:18:08 +0200 Subject: [PATCH 08/11] Remove deprecation notice in docstring --- frictionless/table/header.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frictionless/table/header.py b/frictionless/table/header.py index b9459133d2..8c77375bd3 100644 --- a/frictionless/table/header.py +++ b/frictionless/table/header.py @@ -24,11 +24,6 @@ class Header(List[str]): # type: ignore > Constructor of this object is not Public API - > Deprecated: using a `Header` directly as a list is deprecated, as the list - > ambiguously holds the names of the (possibly inferred) *schema* fields. Use its - > properties instead: `labels` for the header row as read from the data source, - > `fields`/`field_names` for the schema fields. - Parameters: labels (any[]): the header row as read from the data source fields (Field[]): the fields declared in the schema, in schema order From 5d44df93b090dd7fb5727de2bce76f9585a544e4 Mon Sep 17 00:00:00 2001 From: Pierre Camilleri Date: Tue, 28 Jul 2026 12:31:01 +0200 Subject: [PATCH 09/11] =?UTF-8?q?=F0=9F=94=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resource/__spec__/test_validate_schema.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/frictionless/resource/__spec__/test_validate_schema.py b/frictionless/resource/__spec__/test_validate_schema.py index f63904f272..f8922804d9 100644 --- a/frictionless/resource/__spec__/test_validate_schema.py +++ b/frictionless/resource/__spec__/test_validate_schema.py @@ -311,6 +311,31 @@ def test_resource_validate_resource_duplicate_labels_with_sync_schema_issue_910( ] +def test_resource_validate_duplicate_labels_ignoring_header_case(): + # Labels differing only by case match the same field once `header_case` is + # off, so they are duplicates -- and they used to silently collapse onto + # that single field, which was reported as a spurious `extra-cell`. + schema = Schema.from_descriptor( + { + "fields": [{"name": "name", "type": "string"}], + "fieldsMatch": "partial", + } + ) + resource = TableResource( + [["Name", "name"], ["a", "b"]], + schema=schema, + dialect=Dialect(header_case=False), + ) + report = resource.validate() + assert report.flatten(["type", "note"]) == [ + [ + "error", + 'matching fields by name ("fieldsMatch": "partial") ' + "requires unique labels in the header", + ], + ] + + def test_resource_validate_less_actual_fields_with_required_constraint_issue_950(): schema = Schema.describe("data/table.csv") schema.add_field(fields.AnyField(name="bad", constraints={"required": True})) From 85c0b02cb3dd768b48cf91ca7434162674b06115 Mon Sep 17 00:00:00 2001 From: Pierre Camilleri Date: Tue, 28 Jul 2026 12:31:50 +0200 Subject: [PATCH 10/11] =?UTF-8?q?=F0=9F=9F=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frictionless/table/header.py | 2 +- frictionless/table/label_matching.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frictionless/table/header.py b/frictionless/table/header.py index 8c77375bd3..229df3ed4e 100644 --- a/frictionless/table/header.py +++ b/frictionless/table/header.py @@ -155,7 +155,7 @@ def get_expected_fields(self) -> List[Field]: self.__expected_fields = self.__fields return self.__expected_fields - if len(self.__labels) != len(set(self.__labels)): + if self.__matching.has_duplicate_labels: note = ( f'matching fields by name ("fieldsMatch": "{self.__fields_match}") ' "requires unique labels in the header" diff --git a/frictionless/table/label_matching.py b/frictionless/table/label_matching.py index 26d849ea25..b03bb9613f 100644 --- a/frictionless/table/label_matching.py +++ b/frictionless/table/label_matching.py @@ -48,6 +48,16 @@ def unmatched_fields(self) -> List[Field]: if self.__normalize(field.name) not in matched ] + @property + def has_duplicate_labels(self) -> bool: + """Whether two labels match the same field, which makes the mapping ambiguous + + Labels are compared the way they are matched, so under `ignore_case` + two labels differing only by case are duplicates. + """ + keys = [self.__normalize(label) for label in self.__labels] + return len(keys) != len(set(keys)) + @property def has_match(self) -> bool: """Whether at least one label matches a schema field""" From 9e2b255fa240a3869a2cc31872224d769ccc8c89 Mon Sep 17 00:00:00 2001 From: Pierre Camilleri Date: Tue, 28 Jul 2026 12:34:43 +0200 Subject: [PATCH 11/11] remove wordy comment --- frictionless/resource/__spec__/test_validate_schema.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/frictionless/resource/__spec__/test_validate_schema.py b/frictionless/resource/__spec__/test_validate_schema.py index f8922804d9..c7bb612aaf 100644 --- a/frictionless/resource/__spec__/test_validate_schema.py +++ b/frictionless/resource/__spec__/test_validate_schema.py @@ -312,9 +312,6 @@ def test_resource_validate_resource_duplicate_labels_with_sync_schema_issue_910( def test_resource_validate_duplicate_labels_ignoring_header_case(): - # Labels differing only by case match the same field once `header_case` is - # off, so they are duplicates -- and they used to silently collapse onto - # that single field, which was reported as a spurious `extra-cell`. schema = Schema.from_descriptor( { "fields": [{"name": "name", "type": "string"}],