# Licensed under a 3-clause BSD style license - see LICENSE.rst import os import copy from contextlib import nullcontext from io import StringIO from itertools import chain import pytest import numpy as np from astropy.io import ascii from astropy import table from astropy.table.table_helpers import simple_table from astropy.utils.exceptions import AstropyWarning from astropy.utils.compat.optional_deps import HAS_BS4 from astropy.utils.misc import _NOT_OVERWRITING_MSG_MATCH from astropy import units as u from .common import setup_function, teardown_function # noqa if HAS_BS4: from bs4 import BeautifulSoup, FeatureNotFound # noqa test_defs = [ dict(kwargs=dict(), out="""\ ID XCENTER YCENTER MAG MERR MSKY NITER SHARPNESS CHI PIER PERROR 14 138.538 256.405 15.461 0.003 34.85955 4 -0.032 0.802 0 No_error 18 18.114 280.170 22.329 0.206 30.12784 4 -2.544 1.104 0 No_error """ ), dict(kwargs=dict(delimiter=None), out="""\ ID XCENTER YCENTER MAG MERR MSKY NITER SHARPNESS CHI PIER PERROR 14 138.538 256.405 15.461 0.003 34.85955 4 -0.032 0.802 0 No_error 18 18.114 280.170 22.329 0.206 30.12784 4 -2.544 1.104 0 No_error """ ), dict(kwargs=dict(formats={'XCENTER': '%12.1f', 'YCENTER': '{0:.1f}'}, include_names=['XCENTER', 'YCENTER'], strip_whitespace=False), out="""\ XCENTER YCENTER " 138.5" 256.4 " 18.1" 280.2 """ ), dict(kwargs=dict(Writer=ascii.Rdb, exclude_names=['CHI']), out="""\ ID\tXCENTER\tYCENTER\tMAG\tMERR\tMSKY\tNITER\tSHARPNESS\tPIER\tPERROR N\tN\tN\tN\tN\tN\tN\tN\tN\tS 14\t138.538\t256.405\t15.461\t0.003\t34.85955\t4\t-0.032\t0\tNo_error 18\t18.114\t280.170\t22.329\t0.206\t30.12784\t4\t-2.544\t0\tNo_error """ ), dict(kwargs=dict(Writer=ascii.Tab), out="""\ ID\tXCENTER\tYCENTER\tMAG\tMERR\tMSKY\tNITER\tSHARPNESS\tCHI\tPIER\tPERROR 14\t138.538\t256.405\t15.461\t0.003\t34.85955\t4\t-0.032\t0.802\t0\tNo_error 18\t18.114\t280.170\t22.329\t0.206\t30.12784\t4\t-2.544\t1.104\t0\tNo_error """ ), dict(kwargs=dict(Writer=ascii.Csv), out="""\ ID,XCENTER,YCENTER,MAG,MERR,MSKY,NITER,SHARPNESS,CHI,PIER,PERROR 14,138.538,256.405,15.461,0.003,34.85955,4,-0.032,0.802,0,No_error 18,18.114,280.170,22.329,0.206,30.12784,4,-2.544,1.104,0,No_error """ ), dict(kwargs=dict(Writer=ascii.NoHeader), out="""\ 14 138.538 256.405 15.461 0.003 34.85955 4 -0.032 0.802 0 No_error 18 18.114 280.170 22.329 0.206 30.12784 4 -2.544 1.104 0 No_error """ ), dict(kwargs=dict(Writer=ascii.CommentedHeader), out="""\ # ID XCENTER YCENTER MAG MERR MSKY NITER SHARPNESS CHI PIER PERROR 14 138.538 256.405 15.461 0.003 34.85955 4 -0.032 0.802 0 No_error 18 18.114 280.170 22.329 0.206 30.12784 4 -2.544 1.104 0 No_error """ ), dict(kwargs=dict(Writer=ascii.CommentedHeader, comment='&'), out="""\ &ID XCENTER YCENTER MAG MERR MSKY NITER SHARPNESS CHI PIER PERROR 14 138.538 256.405 15.461 0.003 34.85955 4 -0.032 0.802 0 No_error 18 18.114 280.170 22.329 0.206 30.12784 4 -2.544 1.104 0 No_error """ ), dict(kwargs=dict(Writer=ascii.Latex), out="""\ \\begin{table} \\begin{tabular}{ccccccccccc} ID & XCENTER & YCENTER & MAG & MERR & MSKY & NITER & SHARPNESS & CHI & PIER & PERROR \\\\ & pixels & pixels & magnitudes & magnitudes & counts & & & & & perrors \\\\ 14 & 138.538 & 256.405 & 15.461 & 0.003 & 34.85955 & 4 & -0.032 & 0.802 & 0 & No_error \\\\ 18 & 18.114 & 280.170 & 22.329 & 0.206 & 30.12784 & 4 & -2.544 & 1.104 & 0 & No_error \\\\ \\end{tabular} \\end{table} """ ), dict(kwargs=dict(Writer=ascii.AASTex), out="""\ \\begin{deluxetable}{ccccccccccc} \\tablehead{\\colhead{ID} & \\colhead{XCENTER} & \\colhead{YCENTER} & \\colhead{MAG} & \\colhead{MERR} & \\colhead{MSKY} & \\colhead{NITER} & \\colhead{SHARPNESS} & \\colhead{CHI} & \\colhead{PIER} & \\colhead{PERROR}\\\\ \\colhead{ } & \\colhead{pixels} & \\colhead{pixels} & \\colhead{magnitudes} & \\colhead{magnitudes} & \\colhead{counts} & \\colhead{ } & \\colhead{ } & \\colhead{ } & \\colhead{ } & \\colhead{perrors}} \\startdata 14 & 138.538 & 256.405 & 15.461 & 0.003 & 34.85955 & 4 & -0.032 & 0.802 & 0 & No_error \\\\ 18 & 18.114 & 280.170 & 22.329 & 0.206 & 30.12784 & 4 & -2.544 & 1.104 & 0 & No_error \\enddata \\end{deluxetable} """ # noqa ), dict( kwargs=dict(Writer=ascii.AASTex, caption='Mag values \\label{tab1}', latexdict={ 'units': {'MAG': '[mag]', 'XCENTER': '[pixel]'}, 'tabletype': 'deluxetable*', 'tablealign': 'htpb'}), out="""\ \\begin{deluxetable*}{ccccccccccc}[htpb] \\tablecaption{Mag values \\label{tab1}} \\tablehead{\\colhead{ID} & \\colhead{XCENTER} & \\colhead{YCENTER} & \\colhead{MAG} & \\colhead{MERR} & \\colhead{MSKY} & \\colhead{NITER} & \\colhead{SHARPNESS} & \\colhead{CHI} & \\colhead{PIER} & \\colhead{PERROR}\\\\ \\colhead{ } & \\colhead{[pixel]} & \\colhead{pixels} & \\colhead{[mag]} & \\colhead{magnitudes} & \\colhead{counts} & \\colhead{ } & \\colhead{ } & \\colhead{ } & \\colhead{ } & \\colhead{perrors}} \\startdata 14 & 138.538 & 256.405 & 15.461 & 0.003 & 34.85955 & 4 & -0.032 & 0.802 & 0 & No_error \\\\ 18 & 18.114 & 280.170 & 22.329 & 0.206 & 30.12784 & 4 & -2.544 & 1.104 & 0 & No_error \\enddata \\end{deluxetable*} """ # noqa ), dict( kwargs=dict(Writer=ascii.Latex, caption='Mag values \\label{tab1}', latexdict={'preamble': '\\begin{center}', 'tablefoot': '\\end{center}', 'data_end': ['\\hline', '\\hline'], 'units':{'MAG': '[mag]', 'XCENTER': '[pixel]'}, 'tabletype': 'table*', 'tablealign': 'h'}, col_align='|lcccccccccc|'), out="""\ \\begin{table*}[h] \\begin{center} \\caption{Mag values \\label{tab1}} \\begin{tabular}{|lcccccccccc|} ID & XCENTER & YCENTER & MAG & MERR & MSKY & NITER & SHARPNESS & CHI & PIER & PERROR \\\\ & [pixel] & pixels & [mag] & magnitudes & counts & & & & & perrors \\\\ 14 & 138.538 & 256.405 & 15.461 & 0.003 & 34.85955 & 4 & -0.032 & 0.802 & 0 & No_error \\\\ 18 & 18.114 & 280.170 & 22.329 & 0.206 & 30.12784 & 4 & -2.544 & 1.104 & 0 & No_error \\\\ \\hline \\hline \\end{tabular} \\end{center} \\end{table*} """ ), dict(kwargs=dict(Writer=ascii.Latex, latexdict=ascii.latexdicts['template']), out="""\ \\begin{tabletype}[tablealign] preamble \\caption{caption} \\begin{tabular}{col_align} header_start ID & XCENTER & YCENTER & MAG & MERR & MSKY & NITER & SHARPNESS & CHI & PIER & PERROR \\\\ & pixels & pixels & magnitudes & magnitudes & counts & & & & & perrors \\\\ header_end data_start 14 & 138.538 & 256.405 & 15.461 & 0.003 & 34.85955 & 4 & -0.032 & 0.802 & 0 & No_error \\\\ 18 & 18.114 & 280.170 & 22.329 & 0.206 & 30.12784 & 4 & -2.544 & 1.104 & 0 & No_error \\\\ data_end \\end{tabular} tablefoot \\end{tabletype} """ ), dict(kwargs=dict(Writer=ascii.Latex, latexdict={'tabletype': None}), out="""\ \\begin{tabular}{ccccccccccc} ID & XCENTER & YCENTER & MAG & MERR & MSKY & NITER & SHARPNESS & CHI & PIER & PERROR \\\\ & pixels & pixels & magnitudes & magnitudes & counts & & & & & perrors \\\\ 14 & 138.538 & 256.405 & 15.461 & 0.003 & 34.85955 & 4 & -0.032 & 0.802 & 0 & No_error \\\\ 18 & 18.114 & 280.170 & 22.329 & 0.206 & 30.12784 & 4 & -2.544 & 1.104 & 0 & No_error \\\\ \\end{tabular} """ ), dict(kwargs=dict(Writer=ascii.HTML, htmldict={'css': 'table,th,td{border:1px solid black;'}), out="""\
ID XCENTER YCENTER MAG MERR MSKY NITER SHARPNESS CHI PIER PERROR
14 138.538 256.405 15.461 0.003 34.85955 4 -0.032 0.802 0 No_error
18 18.114 280.170 22.329 0.206 30.12784 4 -2.544 1.104 0 No_error
""" ), dict(kwargs=dict(Writer=ascii.Ipac), out="""\ \\MERGERAD='INDEF' \\IRAF='NOAO/IRAFV2.10EXPORT' \\USER='' \\HOST='tucana' \\DATE='05-28-93' \\TIME='14:46:13' \\PACKAGE='daophot' \\TASK='nstar' \\IMAGE='test' \\GRPFILE='test.psg.1' \\PSFIMAGE='test.psf.1' \\NSTARFILE='test.nst.1' \\REJFILE='"hello world"' \\SCALE='1.' \\DATAMIN='50.' \\DATAMAX='24500.' \\GAIN='1.' \\READNOISE='0.' \\OTIME='00:07:59.0' \\XAIRMASS='1.238106' \\IFILTER='V' \\RECENTER='yes' \\FITSKY='no' \\PSFMAG='16.594' \\PSFRAD='5.' \\FITRAD='3.' \\MAXITER='50' \\MAXGROUP='60' \\FLATERROR='0.75' \\PROFERROR='5.' \\CLIPEXP='6' \\CLIPRANGE='2.5' | ID| XCENTER| YCENTER| MAG| MERR| MSKY| NITER| SHARPNESS| CHI| PIER| PERROR| | long| double| double| double| double| double| long| double| double| long| char| | | pixels| pixels| magnitudes| magnitudes| counts| | | | | perrors| | null| null| null| null| null| null| null| null| null| null| null| 14 138.538 256.405 15.461 0.003 34.85955 4 -0.032 0.802 0 No_error 18 18.114 280.170 22.329 0.206 30.12784 4 -2.544 1.104 0 No_error """ # noqa ), ] test_defs_no_data = [ dict(kwargs=dict(Writer=ascii.Ipac), out="""\ \\ This is an example of a valid comment. \\ The 2nd data line is used to verify the exact column parsing \\ (unclear if this is a valid for the IPAC format) \\catalog='sao' \\date='Wed Sp 20 09:48:36 1995' \\mykeyword='Another way for defining keyvalue string' | ra| dec| sai| v2|sptype| |double|double|long|double| char| | unit| unit|unit| unit| ergs| | null| null|null| null| null| """ ), ] tab_to_fill = ['a b c', '1 2 3', '1 1 3'] test_defs_fill_value = [ dict(kwargs=dict(), out="""\ a b c 1 2 3 1 1 3 """ ), dict(kwargs=dict(fill_values=('1', 'w')), out="""\ a b c w 2 3 w w 3 """ ), dict(kwargs=dict(fill_values=('1', 'w', 'b')), out="""\ a b c 1 2 3 1 w 3 """ ), dict(kwargs=dict(fill_values=('1', 'w'), fill_include_names=['b']), out="""\ a b c 1 2 3 1 w 3 """ ), dict(kwargs=dict(fill_values=('1', 'w'), fill_exclude_names=['a']), out="""\ a b c 1 2 3 1 w 3 """ ), dict(kwargs=dict(fill_values=('1', 'w'), fill_include_names=['a'], fill_exclude_names=['a', 'b']), out="""\ a b c 1 2 3 1 1 3 """ ), dict(kwargs=dict(fill_values=[('1', 'w')], formats={'a': '%4.2f'}), out="""\ a b c 1.00 2 3 1.00 w 3 """ ), ] test_def_masked_fill_value = [ dict(kwargs=dict(), out="""\ a b c "" 2 3 1 1 "" """ ), dict(kwargs=dict(fill_values=[('1', 'w'), (ascii.masked, 'X')]), out="""\ a b c X 2 3 w w X """ ), dict(kwargs=dict(fill_values=[('1', 'w'), (ascii.masked, 'XXX')], formats={'a': '%4.1f'}), out="""\ a b c XXX 2 3 1.0 w XXX """ ), dict(kwargs=dict(Writer=ascii.Csv), out="""\ a,b,c ,2,3 1,1, """ ), ] def check_write_table(test_def, table, fast_writer): out = StringIO() try: ascii.write(table, out, fast_writer=fast_writer, **test_def['kwargs']) except ValueError as e: # if format doesn't have a fast writer, ignore if 'not in the list of formats with fast writers' not in str(e.value): raise e return print(f"Expected:\n{test_def['out']}") print(f'Actual:\n{out.getvalue()}') assert [x.strip() for x in out.getvalue().strip().splitlines()] == [ x.strip() for x in test_def['out'].strip().splitlines()] def check_write_table_via_table(test_def, table, fast_writer): out = StringIO() test_def = copy.deepcopy(test_def) if 'Writer' in test_def['kwargs']: format = f"ascii.{test_def['kwargs']['Writer']._format_name}" del test_def['kwargs']['Writer'] else: format = 'ascii' try: table.write(out, format=format, fast_writer=fast_writer, **test_def['kwargs']) except ValueError as e: # if format doesn't have a fast writer, ignore if 'not in the list of formats with fast writers' not in str(e.value): raise e return print(f"Expected:\n{test_def['out']}") print(f'Actual:\n{out.getvalue()}') assert [x.strip() for x in out.getvalue().strip().splitlines()] == [ x.strip() for x in test_def['out'].strip().splitlines()] @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_table(fast_writer): table = ascii.get_reader(Reader=ascii.Daophot) data = table.read('data/daophot.dat') for test_def in test_defs: check_write_table(test_def, data, fast_writer) check_write_table_via_table(test_def, data, fast_writer) @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_fill_values(fast_writer): data = ascii.read(tab_to_fill) for test_def in test_defs_fill_value: check_write_table(test_def, data, fast_writer) @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_fill_masked_different(fast_writer): '''see discussion in #2255''' data = ascii.read(tab_to_fill) data = table.Table(data, masked=True) data['a'].mask = [True, False] data['c'].mask = [False, True] for test_def in test_def_masked_fill_value: check_write_table(test_def, data, fast_writer) @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_no_data_ipac(fast_writer): """Write an IPAC table that contains no data.""" table = ascii.get_reader(Reader=ascii.Ipac) data = table.read('data/no_data_ipac.dat') for test_def in test_defs_no_data: check_write_table(test_def, data, fast_writer) check_write_table_via_table(test_def, data, fast_writer) def test_write_invalid_toplevel_meta_ipac(): """Write an IPAC table that contains no data but has invalid (incorrectly specified) metadata stored in the top-level metadata and therefore should raise a warning, and check that the warning has been raised""" table = ascii.get_reader(Reader=ascii.Ipac) data = table.read('data/no_data_ipac.dat') data.meta['blah'] = 'extra' out = StringIO() with pytest.warns(AstropyWarning, match=r'.*were not written.*') as warn: data.write(out, format='ascii.ipac') assert len(warn) == 1 def test_write_invalid_keyword_meta_ipac(): """Write an IPAC table that contains no data but has invalid (incorrectly specified) metadata stored appropriately in the ``keywords`` section of the metadata but with invalid format and therefore should raise a warning, and check that the warning has been raised""" table = ascii.get_reader(Reader=ascii.Ipac) data = table.read('data/no_data_ipac.dat') data.meta['keywords']['blah'] = 'invalid' out = StringIO() with pytest.warns(AstropyWarning, match=r'.*has been skipped.*') as warn: data.write(out, format='ascii.ipac') assert len(warn) == 1 def test_write_valid_meta_ipac(): """Write an IPAC table that contains no data and has *correctly* specified metadata. No warnings should be issued""" table = ascii.get_reader(Reader=ascii.Ipac) data = table.read('data/no_data_ipac.dat') data.meta['keywords']['blah'] = {'value': 'invalid'} out = StringIO() data.write(out, format='ascii.ipac') @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_comments(fast_writer): """Write comments in output originally read by io.ascii.""" data = ascii.read('#c1\n # c2\t\na,b,c\n# c3\n1,2,3') out = StringIO() ascii.write(data, out, format='basic', fast_writer=fast_writer) expected = ['# c1', '# c2', '# c3', 'a b c', '1 2 3'] assert out.getvalue().splitlines() == expected # header comes before comments for commented-header out = StringIO() ascii.write(data, out, format='commented_header', fast_writer=fast_writer) expected = ['# a b c', '# c1', '# c2', '# c3', '1 2 3'] assert out.getvalue().splitlines() == expected # setting comment=False should disable comment writing out = StringIO() ascii.write(data, out, format='basic', comment=False, fast_writer=fast_writer) expected = ['a b c', '1 2 3'] assert out.getvalue().splitlines() == expected @pytest.mark.parametrize("fast_writer", [True, False]) @pytest.mark.parametrize("fmt", ['%0.1f', '.1f', '0.1f', '{0:0.1f}']) def test_write_format(fast_writer, fmt): """Check different formats for a column.""" data = ascii.read('#c1\n # c2\t\na,b,c\n# c3\n1.11,2.22,3.33') out = StringIO() expected = ['# c1', '# c2', '# c3', 'a b c', '1.1 2.22 3.33'] data['a'].format = fmt ascii.write(data, out, format='basic', fast_writer=fast_writer) assert out.getvalue().splitlines() == expected @pytest.mark.parametrize("fast_writer", [True, False]) def test_strip_names(fast_writer): """Names should be stripped of whitespace by default.""" data = table.Table([[1], [2], [3]], names=(' A', 'B ', ' C ')) out = StringIO() ascii.write(data, out, format='csv', fast_writer=fast_writer) assert out.getvalue().splitlines()[0] == 'A,B,C' def test_latex_units(): """ Check to make sure that Latex and AASTex writers attempt to fall back on the **unit** attribute of **Column** if the supplied **latexdict** does not specify units. """ t = table.Table([table.Column(name='date', data=['a', 'b']), table.Column(name='NUV exp.time', data=[1, 2])]) latexdict = copy.deepcopy(ascii.latexdicts['AA']) latexdict['units'] = {'NUV exp.time': 's'} out = StringIO() expected = '''\ \\begin{table}{cc} \\tablehead{\\colhead{date} & \\colhead{NUV exp.time}\\\\ \\colhead{ } & \\colhead{s}} \\startdata a & 1 \\\\ b & 2 \\enddata \\end{table} '''.replace('\n', os.linesep) ascii.write(t, out, format='aastex', latexdict=latexdict) assert out.getvalue() == expected # use unit attribute instead t['NUV exp.time'].unit = u.s t['date'].unit = u.yr out = StringIO() ascii.write(t, out, format='aastex', latexdict=ascii.latexdicts['AA']) assert out.getvalue() == expected.replace( 'colhead{s}', r'colhead{$\mathrm{s}$}').replace( 'colhead{ }', r'colhead{$\mathrm{yr}$}') @pytest.mark.parametrize("fast_writer", [True, False]) def test_commented_header_comments(fast_writer): """ Test the fix for #3562 with confusing exception using comment=False for the commented_header writer. """ t = table.Table([[1, 2]]) with pytest.raises(ValueError) as err: out = StringIO() ascii.write(t, out, format='commented_header', comment=False, fast_writer=fast_writer) assert "for the commented_header writer you must supply a string" in str(err.value) @pytest.mark.parametrize("fast_writer", [True, False]) def test_byte_string_output(fast_writer): """ Test the fix for #4350 where byte strings were output with a leading `b` on Py3. """ t = table.Table([['Hello', 'World']], dtype=['S10']) out = StringIO() ascii.write(t, out, fast_writer=fast_writer) assert out.getvalue().splitlines() == ['col0', 'Hello', 'World'] @pytest.mark.parametrize('names, include_names, exclude_names, formats, issues_warning', [ (['x', 'y'], ['x', 'y'], ['x'], {'x': '%d', 'y': '%f'}, True), (['x', 'y'], ['x', 'y'], ['y'], {'x': '%d'}, False), (['x', 'y'], ['x', 'y'], [], {'p': '%d', 'q': '%f'}, True), (['x', 'y'], ['x', 'y'], [], {'z': '%f'}, True), (['x', 'y'], ['x', 'y'], [], {'x': '%d'}, False), (['x', 'y'], ['x', 'y'], [], {'p': '%d', 'y': '%f'}, True), (['x', 'y'], ['x', 'y'], [], {}, False) ]) def test_names_with_formats(names, include_names, exclude_names, formats, issues_warning): """Test for #4508.""" t = table.Table([[1, 2, 3], [4.1, 5.2, 6.3]]) out = StringIO() if issues_warning: ctx = pytest.warns(AstropyWarning) else: ctx = nullcontext() with ctx as warn: ascii.write(t, out, names=names, include_names=include_names, exclude_names=exclude_names, formats=formats) if issues_warning: assert len(warn) == 1 @pytest.mark.parametrize('formats, issues_warning', [ ({'p': '%d', 'y': '%f'}, True), ({'x': '%d', 'y': '%f'}, True), ({'z': '%f'}, True), ({}, False) ]) def test_columns_names_with_formats(formats, issues_warning): """Test the fix for #4508.""" t = table.Table([[1, 2, 3], [4.1, 5.2, 6.3]]) out = StringIO() if issues_warning: ctx = pytest.warns(AstropyWarning) else: ctx = nullcontext() with ctx as warn: ascii.write(t, out, formats=formats) if issues_warning: assert len(warn) == 1 @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_quoted_empty_field(fast_writer): """ Test the fix for #4350 where byte strings were output with a leading `b` on Py3. """ t = table.Table([['Hello', ''], ['', '']], dtype=['S10', 'S10']) out = StringIO() ascii.write(t, out, fast_writer=fast_writer) assert out.getvalue().splitlines() == ['col0 col1', 'Hello ""', '"" ""'] out = StringIO() ascii.write(t, out, fast_writer=fast_writer, delimiter=',') assert out.getvalue().splitlines() == ['col0,col1', 'Hello,', ','] @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_empty_table(fast_writer): """Test writing empty table #8275.""" t = table.Table([[]], dtype=['S2']) out = StringIO() ascii.write(t, out, fast_writer=fast_writer) assert out.getvalue().splitlines() == ['col0'] @pytest.mark.parametrize("format", ['ascii', 'csv', 'html', 'latex', 'ascii.fixed_width', 'html']) @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_overwrite_ascii(format, fast_writer, tmpdir): """Test overwrite argument for various ASCII writers""" filename = tmpdir.join("table-tmp.dat").strpath with open(filename, 'w'): # create empty file pass t = table.Table([['Hello', ''], ['', '']], dtype=['S10', 'S10']) with pytest.raises(OSError, match=_NOT_OVERWRITING_MSG_MATCH): t.write(filename, format=format, fast_writer=fast_writer) t.write(filename, overwrite=True, format=format, fast_writer=fast_writer) # If the output is a file object, overwrite is ignored with open(filename, 'w') as fp: t.write(fp, overwrite=False, format=format, fast_writer=fast_writer) t.write(fp, overwrite=True, format=format, fast_writer=fast_writer) fmt_name_classes = list(chain(ascii.core.FAST_CLASSES.items(), ascii.core.FORMAT_CLASSES.items())) @pytest.mark.parametrize("fmt_name_class", fmt_name_classes) def test_roundtrip_masked(fmt_name_class): """ Round trip a simple masked table through every writable format and confirm that reading back gives the same result. """ fmt_name, fmt_cls = fmt_name_class if not getattr(fmt_cls, '_io_registry_can_write', True): return # Skip tests for fixed_width or HTML without bs4 if ((fmt_name == 'html' and not HAS_BS4) or fmt_name == 'fixed_width'): return if 'qdp' in fmt_name: # QDP tables are for numeric values only t = simple_table(masked=True, kinds=['f', 'i']) else: t = simple_table(masked=True) out = StringIO() fast = fmt_name in ascii.core.FAST_CLASSES try: ascii.write(t, out, format=fmt_name, fast_writer=fast) except ImportError: # Some failed dependency, skip test return # No-header formats need to be told the column names kwargs = {'names': t.colnames} if 'no_header' in fmt_name else {} if 'qdp' in fmt_name: kwargs.update({'table_id': 0, 'names': t.colnames}) t2 = ascii.read(out.getvalue(), format=fmt_name, fast_reader=fast, guess=False, **kwargs) assert t.colnames == t2.colnames for col, col2 in zip(t.itercols(), t2.itercols()): assert col.dtype.kind == col2.dtype.kind assert np.all(col == col2) @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_newlines(fast_writer, tmpdir): # Regression test for https://github.com/astropy/astropy/issues/5126 # On windows, when writing to a filename (not e.g. StringIO), newlines were # \r\r\n instead of \r\n. filename = tmpdir.join('test').strpath t = table.Table([['a', 'b', 'c']], names=['col']) ascii.write(t, filename, fast_writer=fast_writer) with open(filename, 'r', newline='') as f: content = f.read() assert content == os.linesep.join(['col', 'a', 'b', 'c']) + os.linesep @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_csv_with_comments(fast_writer): """ Test fix for #7357 where writing a Table with comments to 'csv' fails with a cryptic message. The comments are dropped by default, but when comment='#' is supplied they are still written. """ out = StringIO() t = table.Table([[1, 2], [3, 4]], names=['a', 'b']) t.meta['comments'] = ['hello'] ascii.write(t, out, format='csv', fast_writer=fast_writer) assert out.getvalue().splitlines() == ['a,b', '1,3', '2,4'] out = StringIO() ascii.write(t, out, format='csv', fast_writer=fast_writer, comment='#') assert out.getvalue().splitlines() == ['#hello', 'a,b', '1,3', '2,4'] @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_formatted_mixin(fast_writer): """ Test fix for #8680 where writing a QTable with a quantity mixin generates an exception if a format is specified. """ out = StringIO() t = table.QTable([[1, 2], [1, 2] * u.m], names=['a', 'b']) ascii.write(t, out, fast_writer=fast_writer, formats={'a': '%02d', 'b': '%.2f'}) assert out.getvalue().splitlines() == ['a b', '01 1.00', '02 2.00'] def test_validate_write_kwargs(): out = StringIO() t = table.QTable([[1, 2], [1, 2]], names=['a', 'b']) with pytest.raises(TypeError, match=r"write\(\) argument 'fast_writer' must be a " r"\(, \) object, " r"got instead"): ascii.write(t, out, fast_writer=12) @pytest.mark.parametrize("fmt_name_class", fmt_name_classes) def test_multidim_column_error(fmt_name_class): """ Test that trying to write a multidim column fails in every format except ECSV. """ fmt_name, fmt_cls = fmt_name_class if not getattr(fmt_cls, '_io_registry_can_write', True): return # Skip tests for ecsv or HTML without bs4. See the comment in latex.py # Latex class where max_ndim = None is defined regarding latex and aastex. if ((fmt_name == 'html' and not HAS_BS4) or fmt_name in ('ecsv', 'latex', 'aastex')): return out = StringIO() t = table.Table() t['a'] = np.arange(16).reshape(2, 2, 2, 2) t['b'] = [1, 2] fast = fmt_name in ascii.core.FAST_CLASSES with pytest.raises(ValueError, match=r'column\(s\) with dimension'): ascii.write(t, out, format=fmt_name, fast_writer=fast) @pytest.mark.parametrize("fast_writer", [True, False]) def test_write_as_columns(fast_writer): """ Test that writing a set of columns also roundtrips (as long as the table does not have metadata, etc.) """ # Use masked in case that makes it more difficult. data = ascii.read(tab_to_fill) data = table.Table(data, masked=True) data['a'].mask = [True, False] data['c'].mask = [False, True] data = list(data.columns.values()) for test_def in test_def_masked_fill_value: check_write_table(test_def, data, fast_writer)