Repository: NVIDIA-Merlin/NVTabular

Issue:
[BUG] Test_s3 failing
```
      with s3_context(s3_base=s3_base, bucket=engine, files=files) as s3fs:
            # Create nvt.Dataset from mock s3 paths
            url = f"s3://{engine}" if engine == "parquet" else f"s3://{engine}/*"
            dataset = nvt.Dataset(url, engine=engine, storage_options=s3so)
    
            # Check that the iteration API works
            columns = mycols_pq if engine == "parquet" else mycols_csv
            gdf = nvt.dispatch._concat(list(dataset.to_iter()))[columns]
            assert_eq(gdf.reset_index(drop=True), df.reset_index(drop=True))
    
            cat_names = ["name-cat", "name-string"] if engine == "parquet" else ["name-string"]
            cont_names = ["x", "y", "id"]
            label_name = ["label"]
    
            conts = cont_names >> ops.FillMissing() >> ops.Clip(min_value=0) >> ops.LogOp()
            cats = cat_names >> ops.Categorify(cat_cache="host")
    
            processor = nvt.Workflow(conts + cats + label_name)
            processor.fit(dataset)
    
            # make sure we can write out the dataset back to S3
            # (https://github.com/NVIDIA-Merlin/NVTabular/issues/1214)
>           processor.transform(dataset).to_parquet(f"s3://{engine}/output")

/nvtabular/tests/unit/test_s3.py:111: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/nvtabular/nvtabular/io/dataset.py:906: in to_parquet
    self.schema.write(output_path)
/nvtabular/nvtabular/graph/schema.py:154: in write
    return PbTxt_SchemaWriter.write(self, schema_path)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'nvtabular.graph.schema_io.schema_writer_pbtxt.PbTxt_SchemaWriter'>
schema = [{'name': 'x', 'tags': [<Tags.CONTINUOUS: 'continuous'>], 'properties': {}, 'dtype': <class 'float'>, '_is_list': Fals...int'>, '_is_list': False}, {'name': 'label', 'tags': [], 'properties': {}, 'dtype': dtype('int64'), '_is_list': False}]
schema_path = PosixPath('s3:/csv/output')

    @classmethod
    def write(cls, schema, schema_path):
        schema_path = Path(schema_path)
        if not schema_path.is_dir():
>           raise ValueError(f"The path provided is not a valid directory: {schema_path}")
E           ValueError: The path provided is not a valid directory: s3:/csv/output

/nvtabular/nvtabular/graph/schema_io/schema_writer_pbtxt.py:45: ValueError
```


Discussion:
Regression from here: https://github.com/NVIDIA-Merlin/NVTabular/pull/1218

Resolving patch:
diff --git a/nvtabular/graph/schema_io/schema_writer_pbtxt.py b/nvtabular/graph/schema_io/schema_writer_pbtxt.py
--- a/nvtabular/graph/schema_io/schema_writer_pbtxt.py
+++ b/nvtabular/graph/schema_io/schema_writer_pbtxt.py
@@ -16,6 +16,7 @@
 import os
 from pathlib import Path
 
+import fsspec
 import numpy
 
 os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
@@ -40,9 +41,7 @@ def _read(cls, schema_path):
 
     @classmethod
     def write(cls, schema, schema_path):
-        schema_path = Path(schema_path)
-        if not schema_path.is_dir():
-            raise ValueError(f"The path provided is not a valid directory: {schema_path}")
+        fs = fsspec.get_fs_token_paths(schema_path)[0]
 
         # traverse list of column schema
         schema_file = schema_pb2.Schema()
@@ -51,9 +50,16 @@ def write(cls, schema, schema_path):
             features.append(create_protobuf_feature(col_schema))
         schema_file.feature.extend(features)
 
-        with open(schema_path / "schema.pbtxt", "w") as f:
-            f.write(text_format.MessageToString(schema_file))
-        return schema
+        try:
+            with fs.open(fs.sep.join([str(schema_path), "schema.pbtxt"]), "w") as f:
+                f.write(text_format.MessageToString(schema_file))
+            return schema
+        except Exception as e:
+            if not fs.isdir(schema_path):
+                raise ValueError(
+                    f"The path provided is not a valid directory: {schema_path}"
+                ) from e
+            raise
 
     @classmethod
     def load(cls, schema_path):


Test patch: