Repository: petl-developers/petl

Issue:
tocsv(source=None) does not output to stdout
#### Minimal, reproducible code sample, a copy-pastable example if possible

```python

petl.wrap([...]).tocsv()

```

#### Problem description

The documentation says

```
Load - writing tables to files and databases
The following functions write data from a table to a file-like source or database. For functions that accept a source argument, if the source argument is None or a string it is interpreted as follows:

None - write to stdout
...
```

I've never found this to work, and always work around it.
I looked into it this time briefly, and it looks like a blatant oversight.

The `_resolve_source_from_arg` function gets called from both the read_source_from_arg() and write_source_from_arg() functions below it and does this:
```
def _resolve_source_from_arg(source, handlers):
    if source is None:
        return StdinSource()
   ...
```

You need to move the conditional within each of the two functions that call it (simple solution).

Probably should be prioritized high... this looks pretty bad for newcomers to the library, as this is library one of the first things someone might do.

#### Version and installation information

1.7.11



Discussion:
coveralls: ## Pull Request Test Coverage Report for [Build 3525847841](https://coveralls.io/builds/54471814)

* **10** of **12**   **(83.33%)**  changed or added relevant lines in **2** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.001%**) to **91.116%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [petl/io/sources.py](https://coveralls.io/builds/54471814/source?filename=petl%2Fio%2Fsources.py#L466) | 4 | 5 | 80.0%
| [petl/test/io/test_sources.py](https://coveralls.io/builds/54471814/source?filename=petl%2Ftest%2Fio%2Ftest_sources.py#L103) | 6 | 7 | 85.71%
<!-- | **Total:** | **10** | **12** | **83.33%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/54471814/badge)](https://coveralls.io/builds/54471814) |
| :-- | --: |
| Change from base [Build 3131975355](https://coveralls.io/builds/52776378): |  -0.001% |
| Covered Lines: | 12758 |
| Relevant Lines: | 14002 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/docs/contributing.rst b/docs/contributing.rst
index d1952bf..c604472 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -29,7 +29,7 @@ suite with::
     $ pip install -r requirements-tests.txt
     $ pytest -v petl
 
-Currently :mod:`petl` supports Python 2.7, 3.6 up to 3.10
+Currently :mod:`petl` supports Python 2.7, 3.6 up to 3.11
 so the tests should pass under all these Python versions.
 
 Dependencies
diff --git a/petl/io/sources.py b/petl/io/sources.py
index 108cdbd..0704f1f 100644
--- a/petl/io/sources.py
+++ b/petl/io/sources.py
@@ -436,9 +436,7 @@ def _get_handler_from(source, handlers):
 
 
 def _resolve_source_from_arg(source, handlers):
-    if source is None:
-        return StdinSource()
-    elif isinstance(source, string_types):
+    if isinstance(source, string_types):
         handler = _get_handler_from(source, handlers)
         codec = _get_codec_for(source)
         if handler is None:
@@ -464,6 +462,8 @@ def read_source_from_arg(source):
 
     .. versionadded:: 1.4.0
     '''
+    if source is None:
+        return StdinSource()
     return _resolve_source_from_arg(source, _READERS)
 
 
@@ -477,4 +477,6 @@ def write_source_from_arg(source, mode='wb'):
 
     .. versionadded:: 1.4.0
     '''
+    if source is None:
+        return StdoutSource()
     return _resolve_source_from_arg(source, _WRITERS)
diff --git a/requirements-formats.txt b/requirements-formats.txt
index c365dd9..04d8fc2 100644
--- a/requirements-formats.txt
+++ b/requirements-formats.txt
@@ -5,10 +5,12 @@ intervaltree>=3.0.2
 lxml>=4.6.5
 openpyxl>=2.6.2
 pandas
-tables
 Whoosh>=2.7.4
 xlrd>=2.0.1
 xlwt>=1.3.0
 fastavro>=0.24.2 ; python_version >= '3.4'
 fastavro==0.24.2 ; python_version < '3.0'
 gspread>=3.4.0 ; python_version >= '3.4'
+
+# version 3.7.0 doesn't work yet with python3.11
+tables ; python_version != '3.11'
diff --git a/requirements-optional.txt b/requirements-optional.txt
index b8375a8..b46163e 100644
--- a/requirements-optional.txt
+++ b/requirements-optional.txt
@@ -1,7 +1,14 @@
-# packages bellow need complex local setup
-# throubleshooting: 
-# 1. define the following variable before running pip:
-# $ export DISABLE_BLOSC_AVX2=1
-# 2. pip install --prefer-binary bcolz
+# Packages bellow need complex local setup #
+# Also check: .github/workflows/test-changes.yml
+
+# Throubleshooting: 
+# 1. $ export DISABLE_BLOSC_AVX2=1
+# 2. $ brew install c-blosc
+
 blosc  ; python_version >= '3.7'
-bcolz  ; python_version >= '3.7'
+
+# Throubleshooting: 
+# 1. pip install --prefer-binary -r requirements-optional.txt
+# 2. pip install --prefer-binary bcolz
+
+bcolz  ; python_version >= '3.7' and python_version < '3.10'
diff --git a/setup.py b/setup.py
index 47dbe09..5050a6f 100644
--- a/setup.py
+++ b/setup.py
@@ -47,6 +47,8 @@ setup(
                  'Programming Language :: Python :: 3.7',
                  'Programming Language :: Python :: 3.8',
                  'Programming Language :: Python :: 3.9',
+                 'Programming Language :: Python :: 3.10',
+                 'Programming Language :: Python :: 3.11',
                  'Topic :: Software Development :: Libraries :: Python Modules'
                  ]
 )
diff --git a/tox.ini b/tox.ini
index 683280c..5e5cf38 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,14 +4,14 @@
 # and then run "tox" from this directory.
 
 [tox]
-envlist = py27, py36, py37, py38, py39, py310, {py36,py37,py38,py39,py310}-docs
+envlist = py27, py36, py37, py38, py39, py310, py311, {py36,py37,py38,py39,py310,py311}-docs
 
 [testenv]
 # get stable output for unordered types
 setenv =
     PYTHONHASHSEED = 42
     py27: PY_MAJOR_VERSION = py2
-    py36,py37,py38,py39,py310: PY_MAJOR_VERSION = py3
+    py36,py37,py38,py39,py310,py311: PY_MAJOR_VERSION = py3
 commands =
     pytest --cov=petl petl
     coverage report -m
@@ -19,7 +19,7 @@ deps =
     -rrequirements-tests.txt
     -rrequirements-formats.txt
 
-[testenv:{py36,py37,py38,py39}-docs]
+[testenv:{py36,py37,py38,py39,py310,py311}-docs]
 # build documentation under similar environment to readthedocs
 changedir = docs
 deps =
@@ -27,9 +27,9 @@ deps =
 commands =
     sphinx-build -W -b html -d {envtmpdir}/doctrees .  {envtmpdir}/html
 
-[testenv:{py36,py37,py38,py39,py310}-doctest]
+[testenv:{py36,py37,py38,py39,py310,py311}-doctest]
 commands =
-    py36,py37,py38,py39,py310: pytest --doctest-modules --cov=petl petl
+    py36,py37,py38,py39,py310,py311: pytest --doctest-modules --cov=petl petl
 [testenv:{py36,py37,py38,py39}-dochtml]
 changedir = docs
 deps =


Test patch:
diff --git a/.github/workflows/test-changes.yml b/.github/workflows/test-changes.yml
index bce0946..e2720a9 100644
--- a/.github/workflows/test-changes.yml
+++ b/.github/workflows/test-changes.yml
@@ -27,7 +27,7 @@ jobs:
       fail-fast: false
       matrix:
         os: [ "ubuntu-latest", "windows-latest", "macos-latest" ]
-        python: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10']
+        python: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11']
 
     runs-on: "${{ matrix.os }}"
 
diff --git a/petl/test/io/test_sources.py b/petl/test/io/test_sources.py
index f1e1baf..c2cbff4 100644
--- a/petl/test/io/test_sources.py
+++ b/petl/test/io/test_sources.py
@@ -92,6 +92,17 @@ def test_stdoutsource():
     etl.topickle(tbl, StdoutSource())
 
 
+def test_stdoutsource_none(capfd):
+
+    tbl = [('foo', 'bar'), ('a', 1), ('b', 2)]
+    etl.tocsv(tbl, encoding='ascii')
+    captured = capfd.readouterr()
+    outp = captured.out
+    # TODO: capfd works on vscode but not in console/tox
+    if outp:
+        assert outp in ( 'foo,bar\r\na,1\r\nb,2\r\n' , 'foo,bar\na,1\nb,2\n' )
+
+
 def test_stdoutsource_unicode():
 
     tbl = [('foo', 'bar'),