cloudcat 0.1.3__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudcat
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: A CLI utility to read and display files from cloud storage
5
5
  Home-page: https://github.com/yourusername/cloudcat
6
6
  Author: Your Name
@@ -6,6 +6,7 @@ A command-line utility to read and display data from cloud storage (Google Cloud
6
6
 
7
7
  - **Cloud Storage Support**: Read files from GCS (`gcs://`) and S3 (`s3://`)
8
8
  - **Multiple File Formats**: Support for CSV, JSON, and Parquet
9
+ - **CSV Delimiter Support**: Handle tab-delimited and other custom-delimited files
9
10
  - **Intelligent Directory Handling**: Automatically find and read data from directories
10
11
  - **Multi-File Reading**: Combine data from multiple small files (up to configurable size)
11
12
  - **Streaming**: Optimize to avoid downloading entire files when possible
@@ -36,6 +37,9 @@ pip install cloudcat[all]
36
37
  # Basic file reading
37
38
  cloudcat --path gcs://bucket/file.csv
38
39
 
40
+ # Reading tab-delimited files
41
+ cloudcat --path s3://bucket/data.csv --delimiter "\t"
42
+
39
43
  # Reading from a directory (automatically finds data files)
40
44
  cloudcat --path s3://bucket/spark-output/
41
45
 
@@ -56,6 +60,7 @@ cloudcat --path gcs://bucket/logs/ --multi-file-mode all
56
60
  | `--no-count` | | Disable record count display (counts shown by default) |
57
61
  | `--multi-file-mode` | `-m` | How to handle directories: `auto` (default), `first`, `all` |
58
62
  | `--max-size-mb` | | Maximum size in MB to read for multi-file mode (default: 25) |
63
+ | `--delimiter` | `-d` | Delimiter to use for CSV files (use `\t` for tab) |
59
64
 
60
65
  ## Directory Handling
61
66
 
@@ -71,6 +76,21 @@ When reading directories, CloudCat automatically:
71
76
  - Prioritizes files matching the specified format
72
77
  - Provides a summary of selected files
73
78
 
79
+ ## CSV Delimiter Support
80
+
81
+ CloudCat can handle various CSV delimiter types:
82
+
83
+ ```bash
84
+ # Tab-delimited files
85
+ cloudcat --path gcs://bucket/data.tsv --delimiter "\t"
86
+
87
+ # Pipe-delimited files
88
+ cloudcat --path s3://data/extract.csv --delimiter "|"
89
+
90
+ # Semicolon-delimited files (common in Europe)
91
+ cloudcat --path gcs://reports/data.csv --delimiter ";"
92
+ ```
93
+
74
94
  ## Output Formats
75
95
 
76
96
  - **table**: Formatted table with colored headers (default)
@@ -89,6 +109,16 @@ Make sure you have the appropriate credentials configured before using the tool.
89
109
 
90
110
  ## Example Use Cases
91
111
 
112
+ ### Reading Tab-Delimited Files
113
+
114
+ ```bash
115
+ # Read a tab-delimited file and show as table
116
+ cloudcat --path gcs://analytics/export.tsv --delimiter "\t"
117
+
118
+ # Convert tab-delimited to JSON
119
+ cloudcat --path s3://exports/data.tsv --delimiter "\t" --output-format jsonp
120
+ ```
121
+
92
122
  ### Exploring a Spark Output Directory
93
123
 
94
124
  ```bash
@@ -114,6 +144,9 @@ cloudcat --path gcs://analytics/events.json --columns user_id,event_type,timesta
114
144
  ```bash
115
145
  # Export data as CSV
116
146
  cloudcat --path s3://exports/data.parquet --num-rows 1000 --output-format csv > exported_data.csv
147
+
148
+ # Convert tab-delimited to comma-delimited
149
+ cloudcat --path gcs://exports/data.tsv --delimiter "\t" --output-format csv > converted.csv
117
150
  ```
118
151
 
119
152
  ### Handling Small Files
@@ -123,14 +156,6 @@ cloudcat --path s3://exports/data.parquet --num-rows 1000 --output-format csv >
123
156
  cloudcat --path gcs://logs/daily/ --multi-file-mode all --max-size-mb 100
124
157
  ```
125
158
 
126
- ## Example Outputs
127
-
128
- ### Table Format (default)
129
- ![Table Format Example](https://example.com/table_example.png)
130
-
131
- ### Pretty JSON (jsonp)
132
- ![Pretty JSON Example](https://example.com/jsonp_example.png)
133
-
134
159
  ## License
135
160
 
136
161
  MIT
@@ -2,4 +2,4 @@
2
2
  #!/usr/bin/env python
3
3
  """cloudcat - A CLI utility to read and display files from cloud storage."""
4
4
 
5
- __version__ = "0.1.3"
5
+ __version__ = "0.1.4"
@@ -275,13 +275,16 @@ def get_s3_stream(bucket_name, object_name):
275
275
  return response['Body']
276
276
 
277
277
 
278
- def read_csv_data(stream, num_rows, columns=None):
278
+ def read_csv_data(stream, num_rows, columns=None, delimiter=None):
279
279
  """Read CSV data from a stream."""
280
280
  # First read the data without column filtering to get full schema
281
- if num_rows > 0:
282
- full_df = pd.read_csv(stream, nrows=num_rows)
283
- else:
284
- full_df = pd.read_csv(stream)
281
+ pd_args = {'nrows': num_rows} if num_rows > 0 else {}
282
+
283
+ # Add delimiter if specified
284
+ if delimiter:
285
+ pd_args['delimiter'] = delimiter
286
+
287
+ full_df = pd.read_csv(stream, **pd_args)
285
288
 
286
289
  # Store the full schema for later use
287
290
  full_schema = full_df.dtypes
@@ -384,8 +387,19 @@ def read_parquet_data(stream, num_rows, columns=None):
384
387
  df = table.to_pandas()
385
388
 
386
389
  # Get the full schema as a pandas Series for consistency with other formats
387
- # First, read a small amount of data to get pandas dtypes
388
- full_df = pq.read_table(temp_path, nrows=1).to_pandas()
390
+ # First, read a small sample to get pandas dtypes - use read_row_group to limit rows
391
+ # FIXED: PyArrow doesn't have nrows parameter for read_table
392
+ if parquet_file.num_row_groups > 0:
393
+ # Read just first row group
394
+ sample_table = parquet_file.read_row_group(0)
395
+ if sample_table.num_rows > 1:
396
+ # Slice to just the first row if needed
397
+ sample_table = sample_table.slice(0, 1)
398
+ full_df = sample_table.to_pandas()
399
+ else:
400
+ # If no row groups, create empty DataFrame with correct schema
401
+ full_df = df.iloc[0:0] if not df.empty else pd.DataFrame()
402
+
389
403
  full_schema = full_df.dtypes
390
404
 
391
405
  return df, full_schema
@@ -400,7 +414,7 @@ def read_parquet_data(stream, num_rows, columns=None):
400
414
  pass
401
415
 
402
416
 
403
- def read_data_from_multiple_files(service, bucket, file_list, input_format, num_rows, columns=None):
417
+ def read_data_from_multiple_files(service, bucket, file_list, input_format, num_rows, columns=None, delimiter=None):
404
418
  """Read data from multiple files and concatenate the results."""
405
419
  dfs = []
406
420
  schemas = []
@@ -424,7 +438,7 @@ def read_data_from_multiple_files(service, bucket, file_list, input_format, num_
424
438
 
425
439
  # Read the file
426
440
  if input_format == 'csv':
427
- df, schema = read_csv_data(stream, remaining_rows if remaining_rows > 0 else 0, columns)
441
+ df, schema = read_csv_data(stream, remaining_rows if remaining_rows > 0 else 0, columns, delimiter)
428
442
  elif input_format == 'json':
429
443
  df, schema = read_json_data(stream, remaining_rows if remaining_rows > 0 else 0, columns)
430
444
  elif input_format == 'parquet':
@@ -477,7 +491,7 @@ def read_data_from_multiple_files(service, bucket, file_list, input_format, num_
477
491
  return result_df, full_schema, total_rows
478
492
 
479
493
 
480
- def read_data(service, bucket, object_path, input_format, num_rows, columns=None):
494
+ def read_data(service, bucket, object_path, input_format, num_rows, columns=None, delimiter=None):
481
495
  """Read data from cloud storage."""
482
496
  # Get appropriate stream based on service
483
497
  if service == 'gcs':
@@ -489,7 +503,7 @@ def read_data(service, bucket, object_path, input_format, num_rows, columns=None
489
503
 
490
504
  # Read based on format
491
505
  if input_format == 'csv':
492
- return read_csv_data(stream, num_rows, columns)
506
+ return read_csv_data(stream, num_rows, columns, delimiter)
493
507
  elif input_format == 'json':
494
508
  return read_json_data(stream, num_rows, columns)
495
509
  elif input_format == 'parquet':
@@ -498,7 +512,7 @@ def read_data(service, bucket, object_path, input_format, num_rows, columns=None
498
512
  raise ValueError(f"Unsupported format: {input_format}")
499
513
 
500
514
 
501
- def get_record_count(service, bucket, object_path, input_format):
515
+ def get_record_count(service, bucket, object_path, input_format, delimiter=None):
502
516
  """Get record count from a file."""
503
517
  if input_format == 'parquet' and HAS_PARQUET:
504
518
  # For Parquet, we can get count from metadata
@@ -538,7 +552,13 @@ def get_record_count(service, bucket, object_path, input_format):
538
552
 
539
553
  if input_format == 'csv':
540
554
  chunk_count = 0
541
- for chunk in pd.read_csv(stream, chunksize=10000):
555
+
556
+ # Add delimiter if specified
557
+ read_args = {'chunksize': 10000}
558
+ if delimiter:
559
+ read_args['delimiter'] = delimiter
560
+
561
+ for chunk in pd.read_csv(stream, **read_args):
542
562
  chunk_count += len(chunk)
543
563
  return chunk_count
544
564
  elif input_format == 'json':
@@ -621,7 +641,9 @@ def format_table_with_colored_header(df):
621
641
  help='How to handle directories with multiple files (default: auto)')
622
642
  @click.option('--max-size-mb', default=25, type=int,
623
643
  help='Maximum size in MB to read when reading multiple files (default: 25)')
624
- def main(path, output_format, input_format, columns, num_rows, schema, no_count, multi_file_mode, max_size_mb):
644
+ @click.option('--delimiter', '-d', help='Delimiter to use for CSV files (use "\\t" for tab)')
645
+ def main(path, output_format, input_format, columns, num_rows, schema, no_count,
646
+ multi_file_mode, max_size_mb, delimiter):
625
647
  """Display data from files in Google Cloud Storage or AWS S3.
626
648
 
627
649
  Example usage:
@@ -649,8 +671,16 @@ def main(path, output_format, input_format, columns, num_rows, schema, no_count,
649
671
  \b
650
672
  # Read from multiple files in a directory (up to 25MB)
651
673
  cloudcat --path s3://my-bucket/daily-data/ --multi-file-mode all --max-size-mb 25
674
+
675
+ \b
676
+ # Read a tab-delimited file
677
+ cloudcat --path gcs://my-bucket/data.csv --delimiter "\\t"
652
678
  """
653
679
  try:
680
+ # Handle special characters in delimiter
681
+ if delimiter == "\\t":
682
+ delimiter = "\t"
683
+
654
684
  # Parse the path
655
685
  service, bucket, object_path = parse_cloud_path(path)
656
686
 
@@ -672,7 +702,7 @@ def main(path, output_format, input_format, columns, num_rows, schema, no_count,
672
702
  click.echo(Fore.BLUE + f"Inferred input format: {input_format}" + Style.RESET_ALL)
673
703
 
674
704
  # Read the data from the single file
675
- df, full_schema = read_data(service, bucket, object_path, input_format, num_rows, columns)
705
+ df, full_schema = read_data(service, bucket, object_path, input_format, num_rows, columns, delimiter)
676
706
  total_record_count = None # Will be computed later if needed
677
707
  else:
678
708
  # Read from multiple files
@@ -689,7 +719,7 @@ def main(path, output_format, input_format, columns, num_rows, schema, no_count,
689
719
 
690
720
  # Read data from multiple files
691
721
  df, full_schema, total_record_count = read_data_from_multiple_files(
692
- service, bucket, file_list, input_format, num_rows, columns
722
+ service, bucket, file_list, input_format, num_rows, columns, delimiter
693
723
  )
694
724
 
695
725
  # Update object_path for display/logging purposes
@@ -702,7 +732,7 @@ def main(path, output_format, input_format, columns, num_rows, schema, no_count,
702
732
  click.echo(Fore.BLUE + f"Inferred input format: {input_format}" + Style.RESET_ALL)
703
733
 
704
734
  # Read the data
705
- df, full_schema = read_data(service, bucket, object_path, input_format, num_rows, columns)
735
+ df, full_schema = read_data(service, bucket, object_path, input_format, num_rows, columns, delimiter)
706
736
  total_record_count = None # Will be computed later if needed
707
737
 
708
738
  # Display schema if requested
@@ -718,7 +748,7 @@ def main(path, output_format, input_format, columns, num_rows, schema, no_count,
718
748
  if not no_count:
719
749
  try:
720
750
  if total_record_count is None:
721
- total_record_count = get_record_count(service, bucket, object_path, input_format)
751
+ total_record_count = get_record_count(service, bucket, object_path, input_format, delimiter)
722
752
  click.echo(Fore.CYAN + f"Total records: {total_record_count}" + Style.RESET_ALL)
723
753
  except Exception as e:
724
754
  click.echo(Fore.YELLOW + f"Could not count records: {str(e)}" + Style.RESET_ALL)
@@ -743,7 +773,7 @@ def main(path, output_format, input_format, columns, num_rows, schema, no_count,
743
773
  if not no_count:
744
774
  try:
745
775
  if total_record_count is None:
746
- total_record_count = get_record_count(service, bucket, object_path, input_format)
776
+ total_record_count = get_record_count(service, bucket, object_path, input_format, delimiter)
747
777
  click.echo(Fore.CYAN + f"\nTotal records: {total_record_count}" + Style.RESET_ALL)
748
778
  except Exception as e:
749
779
  click.echo(Fore.YELLOW + f"\nCould not count records: {str(e)}" + Style.RESET_ALL)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudcat
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: A CLI utility to read and display files from cloud storage
5
5
  Home-page: https://github.com/yourusername/cloudcat
6
6
  Author: Your Name
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="cloudcat",
5
- version="0.1.3",
5
+ version="0.1.4",
6
6
  packages=find_packages(),
7
7
  include_package_data=True,
8
8
  install_requires=[
File without changes
File without changes
File without changes