Repository: duckdb/duckdb
File: tools/pythonpkg/tests/fast/api/test_read_csv.py (Python)

Code before:
		# The names are not normalized, so they are capitalized
		assert 'CATEGORY_ID' in column_names

		rel = duckdb_cursor.read_csv(TestFile('category.csv'), normalize_names=True)
		df = rel.df()
		column_names = list(df.columns.values)
		# The capitalized names are normalized to lowercase instead
		assert 'CATEGORY_ID' not in column_names

	def test_filename(self, duckdb_cursor):
		rel = duckdb_cursor.read_csv(TestFile('category.csv'), filename=False)
		df = rel.df()
		column_names = list(df.columns.values)
		# The filename is not included in the returned columns
		assert 'filename' not in column_names

		rel = duckdb_cursor.read_csv(TestFile('category.csv'), filename=True)
		df = rel.df()
		column_names = list(df.columns.values)
		# The filename is included in the returned columns
		assert 'filename' in column_names

	def test_read_filelike(self, duckdb_cursor):
		string = StringIO("c1,c2,c3\na,b,c")
		res = duckdb_cursor.read_csv(string, header=True).fetchall()
		assert res == [('a', 'b', 'c')]

	def test_read_filelike_rel_out_of_scope(self, duckdb_cursor):
		def keep_in_scope():
			string = StringIO("c1,c2,c3\na,b,c")
			# Create a ReadCSVRelation on a file-like object
			# this will add the object to our internal object filesystem
			rel = duckdb_cursor.read_csv(string, header=True)
			# The file-like object will still exist, so we can execute this later
			return rel
		
		def close_scope():
			string = StringIO("c1,c2,c3\na,b,c")
			# Create a ReadCSVRelation on a file-like object
			# this will add the object to our internal object filesystem
			res = duckdb_cursor.read_csv(string, header=True).fetchall()
			# When the relation goes out of scope - we delete the file-like object from our filesystem
			return res

		relation = keep_in_scope()
		res = relation.fetchall()

		res2 = close_scope()
		assert res == res2


Review comment (suggestion):
Could we add some more tests?

* BytesIO
* Other objects that have a read() method maybe?
* What if we pass in a non-readable object (e.g. an integer)
* What if we pass in "None"
* What if read() throws an exception? (perhaps we can make our own class that has a read method that throws an exception?)
* Can we somehow verify the object gets properly deleted? For example, we could make our own class that updates some global in the destructor, e.g. something like this:

```py
global_counter = 0
class MyObject:
  def __init__(self):
    global global_counter
    global_counter += 1
  def __del__(self):
    global global_counter
    global_counter -= 1
```
```py
>>> print(global_counter)
0
>>> obj = MyObject()
>>> print(global_counter)
1
>>> del obj
>>> print(global_counter)
0

```

Code after:
		column_names = list(df.columns.values)
		# The names are not normalized, so they are capitalized
		assert 'CATEGORY_ID' in column_names

		rel = duckdb_cursor.read_csv(TestFile('category.csv'), normalize_names=True)
		df = rel.df()
		column_names = list(df.columns.values)
		# The capitalized names are normalized to lowercase instead
		assert 'CATEGORY_ID' not in column_names

	def test_filename(self, duckdb_cursor):
		rel = duckdb_cursor.read_csv(TestFile('category.csv'), filename=False)
		df = rel.df()
		column_names = list(df.columns.values)
		# The filename is not included in the returned columns
		assert 'filename' not in column_names

		rel = duckdb_cursor.read_csv(TestFile('category.csv'), filename=True)
		df = rel.df()
		column_names = list(df.columns.values)
		# The filename is included in the returned columns
		assert 'filename' in column_names

	def test_read_filelike(self, duckdb_cursor):
		string = StringIO("c1,c2,c3\na,b,c")
		res = duckdb_cursor.read_csv(string, header=True).fetchall()
		assert res == [('a', 'b', 'c')]

	def test_read_filelike_rel_out_of_scope(self, duckdb_cursor):
		def keep_in_scope():
			string = StringIO("c1,c2,c3\na,b,c")
			# Create a ReadCSVRelation on a file-like object
			# this will add the object to our internal object filesystem
			rel = duckdb_cursor.read_csv(string, header=True)
			# The file-like object will still exist, so we can execute this later
			return rel
		
		def close_scope():
			string = StringIO("c1,c2,c3\na,b,c")
			# Create a ReadCSVRelation on a file-like object
			# this will add the object to our internal object filesystem
			res = duckdb_cursor.read_csv(string, header=True).fetchall()
			# When the relation goes out of scope - we delete the file-like object from our filesystem
			return res

		relation = keep_in_scope()
		res = relation.fetchall()

		res2 = close_scope()
		assert res == res2