# Welcome to PyStreamAPI!

Short introduction

Welcome to PyStreamAPI! Here you'll find all the documentation you need to get up and running with Streams in Python!

[![DeepSource](https://deepsource.io/gh/PickwickSoft/pystreamapi.svg/?label=active+issues\&show_trend=true\&token=7lV9pH1U-N1oId03M-XKZL5B)](https://deepsource.io/gh/PickwickSoft/pystreamapi/?ref=repository-badge) [![Tests](https://github.com/PickwickSoft/pystreamapi/actions/workflows/unittests.yml/badge.svg)](https://github.com/PickwickSoft/pystreamapi/actions/workflows/unittests.yml) [![Pylint](https://github.com/PickwickSoft/pystreamapi/actions/workflows/pylint.yml/badge.svg)](https://github.com/PickwickSoft/pystreamapi/actions/workflows/pylint.yml) [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=PickwickSoft_pystreamapi\&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=PickwickSoft_pystreamapi) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=PickwickSoft_pystreamapi\&metric=coverage)](https://sonarcloud.io/summary/new_code?id=PickwickSoft_pystreamapi) [![PyPI - Downloads](https://img.shields.io/pypi/dm/streams.py)](https://pypi.org/project/streams-py/) [![PyPI](https://img.shields.io/pypi/v/streams.py)](https://pypi.org/project/streams-py/)

## What is PyStreamAPI?

<figure><img src="/files/TCQVn40t4BvsOYXAPwXU" alt="PyStreamAPI demo"><figcaption></figcaption></figure>

PyStreamAPI is a Python stream library that draws inspiration from the Java Stream API. Although it closely mirrors the Java API, PyStreamAPI adds some innovative features to make streams in Python even more innovative, declarative and easy to use.

PyStreamAPI offers both sequential and parallel streams and utilizes lazy execution.

Now you might be wondering why another library when there are already a few implementations? Well, here are a few advantages of this particular implementation:

* It provides both sequential and parallel versions.
* Lazy execution is supported, enhancing performance.
* It boasts high speed and efficiency.
* The implementation achieves 100% test coverage.
* It follows Pythonic principles, resulting in clean and readable code.
* It adds some cool innovative features like conditions and an even more declarative look

Let's take a look at a small example:

{% code fullWidth="false" %}

```python
from pystreamapi import Stream

Stream.of([" ", '3', None, "2", 1, ""]) \
    .filter(lambda x: x is not None) \
    .map(str) \
    .map(lambda x: x.strip()) \
    .filter(lambda x: len(x) > 0) \
    .map(int) \
    .sorted() \
    .for_each(print) # Output: 1 2 3
```

{% endcode %}

And here's the equivalent code in Java:

```java
Object[] words = { " ", '3', null, "2", 1, "" };
Arrays.stream( words )
      .filter( Objects::nonNull )
      .map( Objects::toString )
      .map( String::trim )
      .filter( s -> ! s.isEmpty() )
      .map( Integer::parseInt )
      .sorted()
      .forEach( System.out::println );  // Output: 1 2 3
```

### What is a Stream?

A `Stream` is a powerful abstraction for processing sequences of data in a functional and declarative manner. It enables efficient and concise data manipulation and transformation.

Similar to its counterparts in Java and Kotlin, a Stream represents a pipeline of operations that can be applied to a collection or any iterable data source. It allows developers to express complex data processing logic using a combination of high-level operations, promoting code reusability and readability.

With Streams, you can perform a wide range of operations on your data, such as filtering elements, transforming values, aggregating results, sorting, and more. These operations can be seamlessly chained together to form a processing pipeline, where each operation processes the data and passes it on to the next operation.

One of the key benefits of Stream is lazy evaluation. This means that the operations are executed only when the result is actually needed, optimizing resource usage and enabling efficient processing of large or infinite datasets.

Furthermore, Stream supports both sequential and parallel execution. This allows you to leverage parallel processing capabilities when dealing with computationally intensive tasks or large amounts of data, significantly improving performance.

`pystreamapi.Stream` represents a stream that facilitates the execution of one or more operations. Stream operations can be categorized as either intermediate or terminal.

Terminal operations return a result of a specific type, while intermediate operations return the stream itself, enabling method chaining for multi-step operations.

Let's examine an example using Stream:

```python
Stream.of([" ", '3', None, "2", 1, ""]) \
    .filter(lambda x: x is not None) \ # Intermediate operation
    .map(str) \ # Intermediate operation
    .map(lambda x: x.strip()) \ # Intermediate operation
    .filter(lambda x: len(x) > 0) \ # Intermediate operation
    .map(int) \ # Intermediate operation
    .sorted() \ # Intermediate operation
    .for_each(print) # Terminal Operation (Output: 1 2 3)
```

Operations can be performed on a stream either in parallel or sequentially. A parallel stream executes operations concurrently, while a sequential stream processes operations in order.

Considering the above characteristics, a stream can be defined as follows:

* It is not a data structure itself but operates on existing data structures.
* It does not provide indexed access like traditional collections.
* It is designed to work seamlessly with lambda functions, enabling concise and expressive code.
* It facilitates easy aggregation of results into lists, tuples, or sets.
* It can be parallelized, allowing for concurrent execution of operations to improve performance.
* It employs lazy evaluation, executing operations only when necessary.

## Use conditions to speed up your workflow! <a href="#use-conditions-to-speed-up-your-workflow" id="use-conditions-to-speed-up-your-workflow"></a>

<figure><img src="/files/aluBeqYtSh1RcMNAGNYI" alt=""><figcaption><p>Conditions Sample</p></figcaption></figure>

Conditions provide a convenient means for performing logical operations within your Stream, such as using `filter()`, `take_while()`, `drop_while()`, and more. With PyStreamAPI, you have access to a staggering 111 diverse conditions that enable you to process various data types including strings, types, numbers, and dates. Additionally, PyStreamAPI offers a powerful combiner that allows you to effortlessly combine multiple conditions, facilitating the implementation of highly intricate pipelines.

Explore the wide range of possibilities available to you by utilizing conditions here:

{% content-ref url="/pages/Nk65xbp7wqJLvM6ZrVDA" %}
[Conditions](/reference/conditions)
{% endcontent-ref %}

## Error handling: Work with data that you don't know

PyStreamAPI offers a powerful error handling mechanism that allows you to handle errors in a declarative manner. This is especially useful when working with data that you don't know.

{% content-ref url="/pages/V8NBbHwsTRJt4ZMXCZly" %}
[Error handling](/reference/api-reference/error-handling)
{% endcontent-ref %}

## Data loaders: Load data from data files

Data loaders provide a convenient way to process data from CSV, JSON, XML and YAML files in your streams. You can access the values of each data set as if it were an object, containing the header/key names as attributes.

{% content-ref url="/pages/FCasa9LqyooC80BWgouK" %}
[Data Loaders](/reference/data-loaders)
{% endcontent-ref %}

## Want to jump right in?

Feeling like an eager beaver? Jump in to the quick start docs and get making your first Stream:

{% content-ref url="/pages/t8ObAMIiW2YdbRJShd40" %}
[Quick Start](/quick-start)
{% endcontent-ref %}

## Want to deep dive?

Dive a little deeper and start exploring our comprehensive documentation to get an idea of everything that's possible with PyStreamAPI:

{% content-ref url="/pages/zCJp9xukChxq2StkzXEx" %}
[API Reference](/reference/api-reference)
{% endcontent-ref %}


# Quick Start

Get started in just a few seconds!

## Installation

To start using PyStreamAPI just install the core module with this command:

```bash
pip install streams.py
```

If you want to install pystreamapi together with the optional extensions, use this command:

```bash
pip install 'streams.py[all]'
```

This will install pystreamapi together with all optional loader extras: `xml_loader` (XML support) and `json_loader` (streaming JSON support via ijson). TOML and YAML support are included in the core install. You can also install extras individually, as described on the following page:

[Data Loaders](/reference/data-loaders)

Afterward, you can import it with:

```python
from pystreamapi import Stream
```

:tada: PyStreamAPI is now ready to process your data

## Build a new Stream

PyStreamAPI offers two types of Streams, both of which are available in either sequential or parallel versions:

* (Normal) `Stream`: Offers operations that do not depend on the types. The same functionality as Streams in other programming languages.
* `NumericStream`: This stream extends the capabilities of the default stream by introducing numerical operations. It is designed specifically for use with numerical data sources and can only be applied to such data.

There are a few factory methods that create new Streams:

### `Stream.of()`

```python
Stream.of([1, 2, 3]) # Can return a sequential or a parallel stream
```

Using the `of()` method will let the implementation decide which `Stream` to use. If the source is numerical, a `NumericStream` is created.

### `Stream.parallel_of()`

```python
Stream.parallel_of([1, 2, 3]) # Returns a parallel stream (Either normal or numeric)
```

### `Stream.sequential_of()`

```python
Stream.sequential_of([1, 2, 3]) # Returns a sequential stream (Either normal or numeric)
```

### `Stream.of_noneable()`

```python
# Can return a sequential or a parallel stream (Either normal or numeric)
Stream.of_noneable([1, 2, 3])

# Returns a sequential or a parallel, empty stream (Either normal or numeric)
Stream.of_noneable(None) 
```

If the source is `None`, you get an empty `Stream`

### `Stream.iterate()`

```python
Stream.iterate(0, lambda n: n + 2)
```

Creates a Stream of an infinite Iterator created by iterative application of a function f to an initial element seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc.

{% hint style="info" %}
**Note** Do not forget to limit the stream with `.limit()`
{% endhint %}

### `Stream.concat()`

```python
Stream.concat(Stream.of([1, 2]), Stream.of([3, 4])) 
# Like Stream.of([1, 2, 3, 4])
```

Creates a new Stream from multiple Streams. Order doesn't change.


# API Reference

Dive into the specifics of each stream operation by checking out our complete documentation.

## Intermediate Operations

Intermediate operations are transformative and filtering operations applied to the elements of a Stream, enabling diverse data manipulations and facilitating the chaining of operations to construct intricate processing pipelines while maintaining the Stream's continuity.

{% content-ref url="/pages/IJlrFFhIyeiuqg9ZvzNo" %}
[Intermediate Operations](/reference/api-reference/intermediate-operations)
{% endcontent-ref %}

## Terminal Operations

A terminal operation is an operation that is performed on a stream and produces a result or a side effect. Terminal operations are the final step in a stream pipeline and trigger the processing of the elements in the stream.

When a terminal operation is invoked on a stream, it consumes the elements from the stream and produces a result, which could be a single value or a collection, or performs a side effect, such as writing to a file or displaying information on the console. Once a terminal operation is executed, the stream is considered consumed and cannot be reused.

If you try to reuse the stream, it will throw a `RuntimeError`.

{% content-ref url="/pages/C72EyJvS2l3m75a25pjq" %}
[Terminal Operations](/reference/api-reference/terminal-operations)
{% endcontent-ref %}

## Numeric Stream

`NumericStream` is a special Stream type that extends the default functionality with operations for numerical data sources such as statistical and mathematical functions.

{% content-ref url="/pages/HXvLmDuX1EzGYV15uXrk" %}
[Numeric Stream](/reference/api-reference/numeric-stream)
{% endcontent-ref %}


# Intermediate Operations

### **`distinct()` : Remove duplicates**

Returns a stream consisting of the distinct elements of this stream.

```python
Stream.of([1, 1, 2, 3]) \
    .distinct() \
    .to_list() # [1, 2, 3]
```

### **`drop_while()` : Drop elements while the predicate is true**

Returns, if this stream is ordered, a stream consisting of the remaining elements of this stream after dropping the longest prefix of elements that match the given predicate.

```python
Stream.of([1, 2, 3]) \
    .drop_while(lambda x: x < 3) \
    .to_list() # [3]
```

### **`filter()` : Restrict the Stream**

Returns a stream consisting of the elements of this stream that match the given predicate.

```python
Stream.of([1, 2, 3, None]) \
    .filter(lambda x: x is not None) \
    .for_each(print) # 1 2 3
```

### **`flat_map()` : Streams in Streams**

Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element.

```python
Stream.of([1, 2, 3]) \
    .flat_map(lambda x: Stream.of([x, x])) \
    .to_list() # [1, 1, 2, 2, 3, 3]
```

### `group_by()`: Group the stream by a given key

Returns a stream consisting of the elements of this stream, grouped by the given classifier and extracting the key/value pairs.

```python
class Point:
    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Point({self.x}, {self.y})"

Stream.of([Point(1, 2), Point(1, 5), Point(3, 4), Point(3, 1)]) \
    .group_by(lambda p: p.x) \
    .map(lambda g: (g[0], [str(p) for p in g[1]])) \
    .for_each(print)  # (1, ['Point(1, 2)', 'Point(1, 5)'])
                      # (3, ['Point(3, 4)', 'Point(3, 1)'])
```

### `limit()` : Limit the Stream to a certain number of elements

Returns a stream consisting of the elements of this stream, truncated to be no longer than max\_size.

```python
Stream.of([1, 2, 3]) \
    .limit(2) \
    .to_list() # [1, 2]
```

### **`map()` : Convert the elements in the Stream**

Returns a stream consisting of the results of applying the given function to the elements of this stream.

```python
Stream.of([1, "2", 3.0, None]) \
    .map(str) \
    .to_list() # ["1", "2", "3.0", "None"]
```

### `map_to_float()` : Convert the elements in the Stream to a Float

Returns a [`NumericStream`](/reference/api-reference/numeric-stream) consisting of the results of applying the `float()` function to the elements of this stream. Note that this method is not none safe.

```python
Stream.of([1, "2", 3.0]) \
    .map_to_float() \
    .to_list() # [1.0, 2.0, 3.0]
```

### `map_to_int()` : Convert the elements in the Stream to an Integer

Returns a [`NumericStream`](/reference/api-reference/numeric-stream) consisting of the results of applying the `int()` function to the elements of this stream. Note that this method is not none safe.

```python
Stream.of([1, "2", 3.0]) \
    .map_to_int() \
    .to_list() # [1, 2, 3]
```

### `map_to_str()` : Convert the elements in the Stream to a String

Returns a stream consisting of the results of applying the `str()` function to the elements of this stream.

```python
Stream.of([1, 2, 3]) \
    .map_to_str() \
    .to_list() # ["1", "2", "3"]
```

### `numeric()` : Convert the stream to a [`NumericStream`](/reference/api-reference/numeric-stream)

Returns a [`NumericStream`](/reference/api-reference/numeric-stream) consisting of the same elements as the stream contained before conversion.

```python
Stream.of([1, "2", 3.0]) \
    .map(int) \
    .numeric() \
    .sum() # 6
```

### `parallel()` : Convert the stream to a `ParallelStream`

Returns a `ParallelStream` consisting of the same elements as the stream contained before conversion.

```python
Stream.sequential_of([1, 2, 3]) \
    .parallel() \
    .map_to_str() \
    .for_each(print) # "1", "2", "3" (mapped in parallel mode)
```

### `peek()` : View intermediate results

Returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream.

```python
Stream.of([2, 1, 3]) \
    .sorted() \
    .peek(print) \ # 1, 2, 3
    .reversed() \
    .for_each(print) # 3, 2, 1
```

### `reversed()` : Reverse Stream

Returns a stream consisting of the elements of this stream in reverse order.

```python
Stream.of([1, 2, 3]) \
    .reversed() \
    .to_list()  # [3, 2, 1]
```

### `sequential()` : Convert the stream to a `SequentialStream`

Returns a `SequentialStream` consisting of the same elements as the stream contained before conversion.

```python
Stream.parallel_of([1, 2, 3]) \
    .sequential() \
    .map_to_str() \
    .for_each(print) # "1", "2", "3" (mapped in sequential mode)
```

### `skip()` : Skip the first n elements of the Stream

Returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream.

```python
Stream.of([1, 2, 3]) \
    .skip(2) \
    .to_list() # [3]
```

### `sorted()` : Sort Stream

Returns a stream consisting of the elements of this stream, sorted according to natural order or comparator.

```python
Stream.of([2, 9, 1]) \
    .sorted() \
    .to_list()  # [1, 2, 9]
```

Here is an example with a custom comparator:

```python
Stream.of(["a", "cc", "bbb"]) \
    .sorted(lambda x, y: len(y) - len(x)) \
    .to_list()  # ['bbb', 'cc', 'a']
```

### `take_while()` : Take elements while the predicate is true

Returns, if this stream is ordered, a stream consisting of the longest prefix of elements taken from this stream that match the given predicate.

```python
Stream.of([1, 2, 3]) \
    .take_while(lambda x: x < 3) \
    .to_list() # [1, 2]
```


# Terminal Operations

### `all_match()` : Check if all elements match a predicate

Returns whether all elements of this stream match the provided predicate.

```python
Stream.of([1, 2, 3]) \
    .all_match(lambda x: x > 0) # True
```

### **`any_match()` : Check if any element matches a predicate**

Returns whether any elements of this stream match the provided predicate.

```python
Stream.of([1, 2, 3]) \
    .any_match(lambda x: x < 0) # False
```

### `count()` : Count the number of elements in the Stream

Returns the number of elements in this stream.

```python
Stream.of([1, 2, 3]) \
    .count() # 3
```

### `find_any()` : Find an element in the Stream

Returns an Optional describing an arbitrary element of this stream, or an empty Optional if the stream is empty.

```python
Stream.of([1, 2, 3]) \
    .find_any() # Optional[1]
```

### `find_first()` : Find the first element in the Stream

Returns an Optional describing the first element of this stream, or an empty Optional if the stream is empty.

```python
Stream.of([1, 2, 3]) \
    .find_first() # Optional[1]
```

### `for_each()` : Perform an action for each element in the Stream

Performs the provided action for each element of this stream.

```python
Stream.of([1, 2, 3]) \
    .for_each(print) # 1 2 3
```

### `none_match()` : Check if no element matches a predicate

Returns whether no elements of this stream match the provided predicate.

```python
Stream.of([1, 2, 3]) \
    .none_match(lambda x: x < 0) # True
```

### `min()` : Find the minimum element in the Stream

Returns the minimum element of this stream

```python
Stream.of([1, 2, 3]) \
    .min() # 1
```

### **`max()` : Find the maximum element in the Stream**

Returns the maximum element of this stream

```python
Stream.of([1, 2, 3]) \
    .max() # 3
```

### `reduce()` : Reduce the Stream to a single value

Returns the result of reducing the elements of this stream to a single value using the provided reducer.

```python
Stream.of([1, 2, 3]) \
    .reduce(lambda x, y: x + y) # 6
```

### `to_dict()` : Convert the Stream to a dictionary

Returns a dictionary containing the elements of this stream by applying the given classifier.

```python
Stream.of([(1, 2), (1, 6), (2, 3), (2, 8), (3, 0)]) \
    .to_dict(lambda p: p[0]) # {1: [(1, 2), (1, 6)], 2: [(2, 3), (2, 8)], 3: [(3, 0)]}
```

### `to_list()` : Convert the Stream to a List

Returns a list containing the elements of this stream.

```python
Stream.of([1, 2, 3]) \
    .to_list() # [1, 2, 3]
```

### **`to_set()` : Convert the Stream to a Set**

Returns a set containing the elements of this stream.

```python
Stream.of([1, 2, 3]) \
    .to_set() # {1, 2, 3}
```

### **`to_tuple()` : Convert the Stream to a Tuple**

Returns a tuple containing the elements of this stream.

```python
Stream.of([1, 2, 3]) \
    .to_tuple() # (1, 2, 3)
```


# Numeric Stream

{% hint style="info" %}
For information on how to create a `NumericStream` please visit the Quick Start docs: [/pages/t8ObAMIiW2YdbRJShd40#stream.of](https://pystreamapi.pickwicksoft.org/reference/api-reference/pages/t8ObAMIiW2YdbRJShd40#stream.of "mention")
{% endhint %}

### `interquartile_range()`: Calculate the interquartile range

Calculates the interquartile range of a numerical Stream. Returns either `int` or `float`.

```python
Stream.of([1, 2, 3, 4, 5, 7, 7, 8, 9, 9]) \
    .interquartile_range() # Returns 5
```

### `first_quartile()`: Calculate the first quartile

Calculates the first quartile of a numerical Stream. Returns either `int` or `float`.

```python
Stream.of([1, 2, 3, 4, 5, 7, 7, 8, 9, 9]) \
    .first_quartile() # Returns 3
```

### `mean()`: Calculate the mean

Calculates the mean of a numerical Stream. Returns either `int` or `float`.

```python
Stream.of([1, 2, 3, 4, 5, 7, 7, 8, 9, 9]) \
    .mean() # Returns 5.5
```

### `median()`: Calculate the median

Calculates the median of a numerical Stream. Returns either `int` or `float`.

```python
Stream.of([1, 2, 3, 4, 5, 7, 7, 8, 9, 9]) \
    .median() # Returns 6.0
```

### `mode()`: Calculate the mode

Calculates the mode(s) (most frequently occurring element/elements) of a numerical Stream. Returns a list of either `int`, `float`or `None`.&#x20;

```python
Stream.of([1, 2, 3, 4, 5, 7, 7, 8, 9, 9]) \
    .mode() # Returns [7, 9]
```

### `range()`: Calculate the range

Calculates the range of a numerical Stream. Returns either `int` or `float`.

```python
Stream.of([1, 2, 3, 4, 5, 7, 7, 8, 9, 9]) \
    .range() # Returns 8
```

### `third_quartile()`: Calculate the range

Calculates the third quartile of a numerical Stream. Returns either `int` or `float`.

```python
Stream.of([1, 2, 3, 4, 5, 7, 7, 8, 9, 9]) \
    .third_quartile() # Returns 8
```

### `sum()`: Calculate the sum

Calculates the sum of all elements of a numerical Stream. Returns either `int` or `float`.

```python
Stream.of([1, 2, 3]) \
    .sum() # Returns 6
```


# Error handling

Work with data that you don't know

PyStreamAPI offers a powerful error handling mechanism that allows you to handle errors in a declarative manner. This is especially useful when working with data that you don't know.

PyStreamAPI offers three different error levels:

* `ErrorLevel.RAISE`: This is the default error level. It will raise an exception if an error occurs.
* `ErrorLevel.IGNORE`: This error level will ignore any errors that occur and won't inform you.
* `ErrorLevel.WARN`: This error level will warn you about any errors that occur and logs them as a warning with default logger.

You can change the error by using the `error_level(...)` method. All operations following it will use the new level.

```python
from pystreamapi import Stream, ErrorLevel

Stream.of([" ", '3', None, "2", 1, ""]) \
    .error_level(ErrorLevel.IGNORE) \
    .map_to_int() \
    .error_level(ErrorLevel.RAISE) \
    .sorted() \
    .for_each(print) # Output: 1 2 3
```

The code above will ignore all errors that occur during mapping to int and will just skip the elements.

For more details on how to use error handling, please refer to the documentation.

{% hint style="warning" %}
Do not use `ErrorLevel.IGNORE` if you know how to filter out the errors. This could result in unexpected behavior and is against the principles of functional programming.

Error handling is only meant to be used to handle unknown data and it is not intended to be used as a replacement for filtering and proper data validation.
{% endhint %}

Here is an example on how/why you should not use it to replace filtering:

```python
from pystreamapi import Stream, ErrorLevel

class Alien:
    def __int__(self):
        # You never know what the implementation of int() does
        return 1 # You probably do not expect aliens to be represented as int!

print(Stream.of([" ", '3', None, "2", 1, "", Alien()]) \
    .error_level(ErrorLevel.IGNORE) \
    .map_to_int() \
    .sorted() \
    .reduce(lambda x, y: x+y).get()) # Output: 7, not 6 as you might expect
```


# Conditions

Speed up your workflow!

<figure><img src="/files/aluBeqYtSh1RcMNAGNYI" alt=""><figcaption></figcaption></figure>

PyStreamAPI presents four distinct groups of conditions for your convenience. Within these groups, you'll find a variety of conditions with varying parameters. For instance, certain conditions like `even()` do not require any parameters, while others such as `less_than(n)` involve specifying a single parameter.&#x20;

Moreover, PyStreamAPI provides a single combiner that empowers you to seamlessly merge multiple conditions, thereby enhancing your ability to manipulate data more effectively.

### Import

All conditions can be imported from:

```python
from pystreamapi.conditions import …
```

In order to import all conditions from a specific group, use these imports:

```python
from pystreamapi.conditions.numeric import *
from pystreamapi.conditions.types import *
from pystreamapi.conditions.string import *
from pystreamapi.conditions.date import *
```

### Type conditions

{% content-ref url="/pages/27yiiY2oL2yiPs06ahBi" %}
[Type Conditions](/reference/conditions/type-conditions)
{% endcontent-ref %}

### Numeric conditions

{% content-ref url="/pages/pJKRsTAhCAJ15tVbhBo3" %}
[Numeric Conditions](/reference/conditions/numeric-conditions)
{% endcontent-ref %}

### String conditions

{% content-ref url="/pages/BcTTtIqWnFqUdsyZ5P2G" %}
[String Conditions](/reference/conditions/string-conditions)
{% endcontent-ref %}

### Date conditions

{% content-ref url="/pages/nN7X1r2oL5ganzw1pLz1" %}
[Date conditions](/reference/conditions/date-conditions)
{% endcontent-ref %}

### Combiner

`one_of(*conditions)` checks if one of the given conditions are fulfilled. You can pass as many conditions as you want.

```python
from pystreamapi import Stream
from pystreamapi.conditions import prime, even, one_of

Stream.of([1, 2, 3, 4, 5]) \
    .filter(one_of(even(), prime())) \
    .for_each(print)
```


# Type Conditions

### `of_type(cls)`: Check if object is of type

Checks if an element is an instance of the specified class.

```python
Stream.of([1, 3.4, "A", None] \
    .filter(of_type(int)) \
    .for_each(print) # 1
```

### `not_of_type(cls: Type)`: Check if object is not of type

Checks if an element is not an instance of the specified class.

```python
Stream.of([1, 3.4, "A", None] \
   .filter(not_of_type(int)) \
   .for_each(print) # 3.4, "A", None
```

### `none()`: Check if object is None

Checks if an element is `None`.

```python
Stream.of([1, None, "Hello", None] \
   .filter(none()) \
   .for_each(print) # None, None
```

### `not_none()`: Check if object is not None

Checks if an element is not `None`.

```python
Stream.of([1, None, "Hello", None] \
   .filter(not_none()) \
   .for_each(print) # 1, "Hello"
```

### `true()`: Check if object is True

Checks if an element is `True`.

```python
Stream.of([True, False, "Yes", 0] \
   .filter(true()) \
   .for_each(print) # True
```

### `not_true()`: Check if object is not True

Checks if an element is not `True`.

```python
Stream.of([True, False, "Yes", 0] \
   .filter(not_true()) \
   .for_each(print) # False, "Yes", 0
```

### `false()`: Check if object is False

Checks if an element is `False`.

```python
Stream.of([True, False, "Yes", 0] \
   .filter(false()) \
   .for_each(print) # False
```

### `not_false()`: Check if object is not False

Checks if an element is not `False`.

```python
Stream.of([True, False, "Yes", 0] \
   .filter(not_false()) \
   .for_each(print) # True, "Yes", 0
```

### `length(x)`: Check if object has specified length

Checks if an element has the specified length.

```python
Stream.of(["apple", "banana", "cherry", "kiwi"] \
   .filter(length(5)) \
   .for_each(print) # apple
```

### `not_length(x)`: Check if object does not have specified length

Checks if an element does not have the specified length.

```python
Stream.of(["apple", "banana", "cherry", "kiwi"] \
   .filter(not_length(6)) \
   .for_each(print) # apple, kiwi
```

### `empty()`: Check if object is empty

Checks if an element is empty (e.g., an empty list, string, etc.).

```python
Stream.of([[], "", {}, set(), None, 0] \
    .filter(empty()) \
    .for_each(print) # [], "", {}, set()
```

### `not_empty()`: Check if object is not empty

Checks if an element is not empty.

```python
Stream.of([[], "", {}, set(), None, 0] \
    .filter(not_empty()) \
    .for_each(print) # None, 0
```

### `equal(x)`: Check if object is equal to the specified value

Checks if an element is equal to the specified value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(equal(3)) \
    .for_each(print) # 3
```

### `not_equal(x)`: Check if object is not equal to the specified value

Checks if an element is not equal to the specified value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(not_equal(3)) \
    .for_each(print) # 1, 2, 4, 5
```


# Numeric Conditions

### `even()`: Check if number is even

Returns a condition that checks if a number is even.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(even()) \
    .for_each(print) # 2, 4
```

### `odd()`: Check if number is odd

Returns a condition that checks if a number is odd.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(odd()) \
    .for_each(print) # 1, 3, 5
```

### `positive()`: Check if number is positive

Returns a condition that checks if a number is positive.

```python
Stream.of([-1, 0, 2, -3, 4] \
    .filter(positive()) \
    .for_each(print) # 2, 4
```

### `negative()`: Check if number is negative

Returns a condition that checks if a number is negative.

```python
Stream.of([-1, 0, 2, -3, 4] \
    .filter(negative()) \
    .for_each(print) # -1, -3
```

### `zero()`: Check if number is zero

Returns a condition that checks if a number is zero.

```python
Stream.of([-1, 0, 2, -3, 4] \
    .filter(zero()) \
    .for_each(print) # 0
```

### `non_zero()`: Check if number is non-zero

Returns a condition that checks if a number is non-zero.

```python
Stream.of([-1, 0, 2, -3, 4] \
    .filter(non_zero()) \
    .for_each(print) # -1, 2, -3, 4
```

### `greater_than(n)`: Check if number is greater than a given value

Returns a condition that checks if a number is greater than a given value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(greater_than(3)) \
    .for_each(print) # 4, 5
```

### `greater_than_or_equal(n)`: Check if number is greater than or equal to a given value

Returns a condition that checks if a number is greater than or equal to a given value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(greater_than_or_equal(3)) \
    .for_each(print) # 3, 4, 5
```

### `less_than(n)`: Check if number is less than a given value

Returns a condition that checks if a number is less than a given value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(less_than(3)) \
    .for_each(print) # 1, 2
```

### `less_than_or_equal(n)`: Check if number is less than or equal to a given value

Returns a condition that checks if a number is less than or equal to a given value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(less_than_or_equal(3)) \
    .for_each(print) # 1, 2, 3
```

### `between(minimum, maximum)`: Check if number is between two given values

Returns a condition that checks if a number is between two given values (inclusive).

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(between(2, 4)) \
    .for_each(print) # 2, 3, 4
```

### `not_between(minimum, maximum)`: Check if number is not between two given values

Returns a condition that checks if a number is not between two given values (inclusive).

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(not_between(2, 4)) \
    .for_each(print) # 1, 5
```

### `equal_to(n)`: Check if number is equal to a given value

Returns a condition that checks if a number is equal to a given value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(equal_to(3)) \
    .for_each(print) # 3
```

### `not_equal_to(n)`: Check if number is not equal to a given value

Returns a condition that checks if a number is not equal to a given value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(not_equal_to(3)) \
    .for_each(print) # 1, 2, 4, 5
```

### `multiple_of(n)`: Check if number is a multiple of a given value

Returns a condition that checks if a number is a multiple of a given value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(multiple_of(2)) \
    .for_each(print) # 2, 4
```

### `not_multiple_of(n)`: Check if number is not a multiple of a given value

Returns a condition that checks if a number is not a multiple of a given value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(not_multiple_of(2)) \
    .for_each(print) # 1, 3, 5
```

### `divisor_of(n)`: Check if number is a divisor of a given value

Returns a condition that checks if a number is a divisor of a given value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(divisor_of(12)) \
    .for_each(print) # 1, 2, 3, 4, 6, 12
```

### `not_divisor_of(n)`: Check if number is not a divisor of a given value

Returns a condition that checks if a number is not a divisor of a given value.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(not_divisor_of(12)) \
    .for_each(print) # 5
```

### `prime()`: Check if number is prime

Returns a condition that checks if a number is prime.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(prime()) \
    .for_each(print) # 2, 3, 5
```

### `not_prime()`: Check if number is not prime

Returns a condition that checks if a number is not prime.

```python
Stream.of([1, 2, 3, 4, 5]

 \
    .filter(not_prime()) \
    .for_each(print) # 1, 4
```

### `perfect_square()`: Check if number is a perfect square

Returns a condition that checks if a number is a perfect square.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(perfect_square()) \
    .for_each(print) # 1, 4
```

### `not_perfect_square()`: Check if number is not a perfect square

Returns a condition that checks if a number is not a perfect square.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(not_perfect_square()) \
    .for_each(print) # 2, 3, 5
```

### `perfect_cube()`: Check if number is a perfect cube

Returns a condition that checks if a number is a perfect cube.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(perfect_cube()) \
    .for_each(print) # 1
```

### `not_perfect_cube()`: Check if number is not a perfect cube

Returns a condition that checks if a number is not a perfect cube.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(not_perfect_cube()) \
    .for_each(print) # 2, 3, 4, 5
```

### `perfect_power()`: Check if number is a perfect power

Returns a condition that checks if a number is a perfect power.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(perfect_power()) \
    .for_each(print) # 1, 4
```

### `not_perfect_power()`: Check if number is not a perfect power

Returns a condition that checks if a number is not a perfect power.

```python
Stream.of([1, 2, 3, 4, 5] \
    .filter(not_perfect_power()) \
    .for_each(print) # 2, 3, 5
```

### `palindrome()`: Check if number is a palindrome

Returns a condition that checks if a number is a palindrome.

```python
Stream.of([12321, 456, 78987] \
    .filter(palindrome()) \
    .for_each(print) # 12321, 78987
```

### `not_palindrome()`: Check if number is not a palindrome

Returns a condition that checks if a number is not a palindrome.

```python
Stream.of([12321, 456, 78987] \
    .filter(not_palindrome()) \
    .for_each(print) # 456
```

### `armstrong()`: Check if number is an Armstrong number

Returns a condition that checks if a number is an Armstrong number.

```python
Stream.of([153, 370, 9474] \
    .filter(armstrong()) \
    .for_each(print) # 153, 370, 9474
```

### `not_armstrong()`: Check if number is not an Armstrong number

Returns a condition that checks if a number is not an Armstrong number.

```python
Stream.of([153, 370, 9474] \
    .filter(not_armstrong()) \
    .for_each(print) # None
```

### `narcissistic()`: Check if number is a narcissistic number

Returns a condition that checks if a number is a narciss

istic number.

```python
Stream.of([153, 370, 9474] \
    .filter(narcissistic()) \
    .for_each(print) # 153, 370, 9474
```

### `not_narcissistic()`: Check if number is not a narcissistic number

Returns a condition that checks if a number is not a narcissistic number.

```python
Stream.of([153, 370, 9474] \
    .filter(not_narcissistic()) \
    .for_each(print) # None
```

### `happy()`: Check if number is a happy number

Returns a condition that checks if a number is a happy number.

```python
Stream.of([19, 32, 86] \
    .filter(happy()) \
    .for_each(print) # 19, 32
```

### `sad()`: Check if number is a sad number

Returns a condition that checks if a number is a sad number.

```python
Stream.of([19, 32, 86] \
    .filter(sad()) \
    .for_each(print) # 86
```

### `abundant()`: Check if number is an abundant number

Returns a condition that checks if a number is an abundant number.

```python
Stream.of([12, 16, 28] \
    .filter(abundant()) \
    .for_each(print) # 12, 16, 28
```

### `not_abundant()`: Check if number is not an abundant number

Returns a condition that checks if a number is not an abundant number.

```python
Stream.of([12, 16, 28] \
    .filter(not_abundant()) \
    .for_each(print) # None
```

### `deficient()`: Check if number is a deficient number

Returns a condition that checks if a number is a deficient number.

```python
Stream.of([12, 16, 28] \
    .filter(deficient()) \
    .for_each(print) # None
```

### `not_deficient()`: Check if number is not a deficient number

Returns a condition that checks if a number is not a deficient number.

```python
Stream.of([12, 16, 28] \
    .filter(not_deficient()) \
    .for_each(print) # 12, 16, 28
```

### `perfect()`: Check if number is a perfect number

Returns a condition that checks if a number is a perfect number.

```python
Stream.of([6, 28, 496] \
    .filter(perfect()) \
    .for_each(print) # 6, 28, 496
```

### `not_perfect()`: Check if number is not a perfect number

Returns a condition that checks if a number is not a perfect number.

```python
Stream.of([6, 28, 496] \
    .filter(not_perfect()) \
    .for_each(print) # None
```


# String Conditions

### `contains(x)`: Check if string contains a substring

Returns a condition that checks if a string contains a specified substring.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(contains("na")) \
    .for_each(print) # banana
```

### `not_contains(x)`: Check if string does not contain a substring

Returns a condition that checks if a string does not contain a specified substring.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(not_contains("na")) \
    .for_each(print) # apple, cherry
```

### `starts_with(x)`: Check if string starts with a substring

Returns a condition that checks if a string starts with a specified substring.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(starts_with("ba")) \
    .for_each(print) # banana
```

### `ends_with(x)`: Check if string ends with a substring

Returns a condition that checks if a string ends with a specified substring.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(ends_with("ry")) \
    .for_each(print) # cherry
```

### `matches(x)`: Check if string matches a regular expression pattern

Returns a condition that checks if a string matches a specified regular expression pattern.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(matches("^a.*e$")) \
    .for_each(print) # apple
```

### `not_matches(x)`: Check if string does not match a regular expression pattern

Returns a condition that checks if a string does not match a specified regular expression pattern.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(not_matches("^a.*e$")) \
    .for_each(print) # banana, cherry
```

### `longer_than(x)`: Check if string is longer than a specified length

Returns a condition that checks if a string is longer than a specified length.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(longer_than(5)) \
    .for_each(print) # banana, cherry
```

### `shorter_than(x)`: Check if string is shorter than a specified length

Returns a condition that checks if a string is shorter than a specified length.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(shorter_than(6)) \
    .for_each(print) # apple
```

### `longer_than_or_equal(x)`: Check if string is longer than or equal to a specified length

Returns a condition that checks if a string is longer than or equal to a specified length.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(longer_than_or_equal(6)) \
    .for_each(print) # banana, cherry
```

### `shorter_than_or_equal(x)`: Check if string is shorter than or equal to a specified length

Returns a condition that checks if a string is shorter than or equal to a specified length.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(shorter_than_or_equal(5)) \
    .for_each(print) # apple
```

### `equal_to_ignore_case(x)`: Check if string is equal to another string (case-insensitive)

Returns a condition that checks if a string is equal to another string, ignoring the case.

```python
Stream.of(["apple", "banana", "cherry"] \


    .filter(equal_to_ignore_case("BANANA")) \
    .for_each(print) # banana
```

### `not_equal_to_ignore_case(x)`: Check if string is not equal to another string (case-insensitive)

Returns a condition that checks if a string is not equal to another string, ignoring the case.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(not_equal_to_ignore_case("BANANA")) \
    .for_each(print) # apple, cherry
```

### `contains_ignore_case(x)`: Check if string contains a substring (case-insensitive)

Returns a condition that checks if a string contains a specified substring, ignoring the case.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(contains_ignore_case("AN")) \
    .for_each(print) # apple, banana
```

### `not_contains_ignore_case(x)`: Check if string does not contain a substring (case-insensitive)

Returns a condition that checks if a string does not contain a specified substring, ignoring the case.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(not_contains_ignore_case("AN")) \
    .for_each(print) # cherry
```

### `starts_with_ignore_case(x)`: Check if string starts with a substring (case-insensitive)

Returns a condition that checks if a string starts with a specified substring, ignoring the case.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(starts_with_ignore_case("BA")) \
    .for_each(print) # banana
```

### `ends_with_ignore_case(x)`: Check if string ends with a substring (case-insensitive)

Returns a condition that checks if a string ends with a specified substring, ignoring the case.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(ends_with_ignore_case("RY")) \
    .for_each(print) # cherry
```

### `matches_ignore_case(x)`: Check if string matches a regular expression pattern (case-insensitive)

Returns a condition that checks if a string matches a specified regular expression pattern, ignoring the case.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(matches_ignore_case("^A.*E$")) \
    .for_each(print) # apple
```

### `not_matches_ignore_case(x)`: Check if string does not match a regular expression pattern (case-insensitive)

Returns a condition that checks if a string does not match a specified regular expression pattern, ignoring the case.

```python
Stream.of(["apple", "banana", "cherry"] \
    .filter(not_matches_ignore_case("^A.*E$")) \
    .for_each(print) # banana, cherry
```


# Date conditions

All date conditions can be used either with `datetime.datetime` or with `datetime.date`. All examples on this page are using `datetime`, but can be replaced by `date`.

### `before(date)`: Check if date is before another date

Check if a datetime/date is before a given datetime/date.

```python
Stream.of([datetime.now() - timedelta(days=1)])\
    .filter(before(datetime.now()))\
    .for_each(print)  # Output: 2023-06-01 17:03:54.386812
```

### `after(date)`: Check if date is after another date

Check if a datetime/date is after a given datetime/date.

```python
Stream.of([datetime.now() + timedelta(days=1)])\
    .filter(after(datetime.now()))\
    .for_each(print)  # Output: 2023-06-03 17:03:54.386812
```

### `before_or_equal(date)`: Check if date is before or equal to another date

Check if a datetime/date is before or equal to a given datetime/date.

```python
Stream.of([datetime.now() - timedelta(days=1)])\
    .filter(before_or_equal(datetime.now()))\
    .for_each(print)  # Output: 2023-06-01 17:03:54.386812
```

### `after_or_equal(date)`: Check if date is after or equal to another date

Check if a datetime/date is after or equal to a given datetime/date.

```python
Stream.of([datetime.now() + timedelta(days=1)])\
    .filter(after_or_equal(datetime.now()))\
    .for_each(print)  # Output: 2023-06-03 17:03:54.386812
```

### `between_or_equal(start_date, end_date)`: Check if date is between or equal to two dates

Check if a datetime/date is between or equal to two given datetimes/date.

```python
Stream.of([datetime.now() - timedelta(days=2)])\
    .filter(between_or_equal(datetime.now() - timedelta(days=3), datetime.now() - timedelta(days=1)))\
    .for_each(print)  # Output: 2023-06-01 17:03:54.386812
```

### `not_between_or_equal(start_date, end_date)`: Check if date is not between or equal to two dates

Check if a datetime/date is not between or equal to two given datetimes/dates.

```python
Stream.of([datetime.now() - timedelta(days=2)])\
    .filter(not_between_or_equal(datetime.now() - timedelta(days=3), datetime.now() - timedelta(days=1)))\
    .for_each(print)  # Output: (no output)
```

### `today()`: Check if date is today

Check if a datetime/date is today.

```python
Stream.of([datetime.now()])\
    .filter(today())\
    .for_each(print)  # Output: 2023-06-02 17:03:54.386812
```

### `today_utc()`: Check if date is today in UTC

Check if a datetime/date is today (in UTC).

```python
Stream.of([datetime.now(timezone.utc)])\
    .filter(today_utc())\
    .for_each(print)  # Output: 2023-06-02 17:03:54.386812
```

### `yesterday()`: Check if date is yesterday

Check if a datetime/date is yesterday.

```python
Stream.of([datetime.now() - timedelta(days=1)])\
    .filter(yesterday())\
    .for_each(print)  # Output: 2023-06-01 17:03:54.386812
```

### `yesterday_utc()`: Check if date is yesterday in UTC

Check if a datetime/date is yesterday (in UTC).

```python
Stream.of([datetime.now(timezone.utc) - timedelta(days=1)])\
    .filter(yesterday_utc())\
    .for_each(print)  # Output: 2023-06-01 17:03:54.386812
```

### `tomorrow()`: Check if date is tomorrow

Check if a datetime/date is tomorrow.

```python
Stream.of([datetime.now() + timedelta(days=1)])\
    .filter(tomorrow())\
    .for_each(print)  # Output: 2023-06-03 17:03:54.386812
```

### `tomorrow_utc()`: Check if date is tomorrow in UTC

Check if a datetime/date is tomorrow (in UTC).

```python
Stream.of([datetime.now(timezone.utc) + timedelta(days=1)])\
    .filter(tomorrow_utc())\
    .for_each(print)  # Output: 2023-06-03 17:03:54.386812
```

### `this_week()`: Check if date is within the current week

Check if a datetime/date is within the current week.

```python
Stream.of([datetime.now()])\
    .filter(this_week())\
    .for_each(print)  # Output: 2023-06-02 17:03:54.386812
```

### `this_week_utc()`: Check if date is within the current week in UTC

Check if a datetime/date is within the current week (in UTC).

```python
Stream.of([datetime.now(timezone.utc)])\
    .filter(this_week_utc())\
    .for_each(print)  # Output: 2023-06-02 17:03:54.386812
```

### `last_week()`: Check if date is within the previous week

Check if a datetime/date is within the previous week.

```python
Stream.of([datetime.now() - timedelta(weeks=1)])\
    .filter(last_week())\
    .for_each(print)  # Output: 2023-05-26 17:03:54.386812
```

### `last_week_utc()`: Check if date is within the previous week in UTC

Check if a datetime/date is within the previous week (in UTC).

```python
Stream.of([datetime.now(timezone.utc) - timedelta(weeks=1)])\
    .filter(last_week_utc())\
    .for_each(print)  # Output: 2023-05-26 17:03:54.386812
```

### `next_week()`: Check if date is within the next week

Check if a datetime/date is within the next week.

```python
Stream.of([datetime.now() + timedelta(weeks=1)])\
    .filter(next_week())\
    .for_each(print)  # Output: 2023-06-09 17:03:54.386812
```

### `next_week_utc()`: Check if date is within the next week in UTC

Check if a datetime/date is within the next week (in UTC).

```python
Stream.of([datetime.now(timezone.utc) + timedelta(weeks=1)])\
    .filter(next_week_utc())\
    .for_each(print)  # Output: 2023-06-09 17:03:54.386812
```

### `this_month()`: Check if date is within the current month

Check if a datetime/date is within the current month.

```python
Stream.of([datetime.now()])\
    .filter(this_month())\
    .for_each(print)  # Output: 2023-06-02 17:03:54.386812
```

### `this_month_utc()`: Check if date is within the current month in UTC

Check if a datetime/date is within the current month (in UTC).

```python
Stream.of([datetime.now(timezone.utc)])\
    .filter(this_month_utc())\
    .for_each(print)  # Output: 2023-06-02 17:03:54.386812
```

### `last_month()`: Check if date is within the previous month

Check if a datetime/date is within the previous month.

```python
Stream.of([datetime.now() - relativedelta(months=1)])\
    .filter(last_month())\
    .for_each(print)  # Output: 2023-05-02 17:03:54.386812
```

### `last_month_utc()`: Check if date is within the previous month in UTC

Check if a datetime/date is within the previous month (in UTC).

```python
Stream.of([datetime.now(timezone.utc) - relativedelta(months=1)])\
    .filter(last_month_utc())\
    .for_each(print)  # Output: 2023-05-02 17:03:54.386812
```

### `next_month()`: Check if date is within the next month

Check if a datetime/date is within the next month.

```python
Stream.of([datetime.now() + relativedelta(months=1)])\
    .filter(next_month())\
    .for_each(print)  # Output: 2023-07-02 17:03:54.386812
```

### `next_month_utc()`: Check if date is within the next month in UTC

Check if a datetime/date is within the next month (in UTC).

```python
Stream.of([datetime.now(timezone.utc) + relativedelta(months=1)])\
    .filter(next_month_utc())\
    .for_each(print)  # Output: 2023-07-02 17:03:54.386812
```

### `this_year()`: Check if date is within the current year

Check if a datetime/date is within the current year.

```python
Stream.of([datetime.now()])\
    .filter(this_year())\
    .for_each(print)  # Output: 2023-06-02 17:03:54.386812
```

### `this_year_utc()`: Check if date is within the current year in UTC

Check if a datetime/date is within the current year (in UTC).

```python
Stream.of([datetime.now(timezone.utc)])\
    .filter(this_year_utc())\
    .for_each(print)  # Output: 2023-06-02 17:03:54.386812
```

### `last_year()`: Check if date is within the previous year

Check if a datetime/date is within the previous year.

```python
Stream.of([datetime.now() - relativedelta(years=1)])\
    .filter(last_year())\
    .for_each(print)  # Output: 2022-06-02 17:03:54.386812
```

### `last_year_utc()`: Check if date is within the previous year in UTC

Check if a datetime/date is within the previous year (in UTC).

```python
Stream.of([datetime.now(timezone.utc) - relativedelta(years=1)])\
    .filter(last_year_utc())\
    .for_each(print)  # Output: 2022-06-02 17:03:54.386812
```

### `next_year()`: Check if date is within the next year

Check if a datetime/date is within the next year.

```python
Stream.of([datetime.now() + relativedelta(years=1)])\
    .filter(next_year())\
    .for_each(print)  # Output: 2024-06-02 17:03:54.386812
```

### `next_year_utc()`: Check if date is within the next year in UTC

Check if a datetime/date is within the next year (in UTC).

```python
Stream.of([datetime.now(timezone.utc) + relativedelta(years=1)])\
    .filter(next_year_utc())\
    .for_each(print)  # Output: 2024-06-02 17:03:54.386812
```


# Data Loaders

Data loaders provide a convenient way to process data from various data files in your streams. You can access the values of each data set as if it were an object, containing the header names as attributes.

{% hint style="info" %}
Currently, PyStreamAPI supports reading data from CSV, JSON, XML, YAML and TOML files.
{% endhint %}

To use the loaders, you can import them with this line:

```python
from pystreamapi.loaders import csv, json, toml, xml, yaml
```

### CSV loader

In order to load the data from a CSV file, you can use the `csv` loader.

You just need the file's path, and you can optionally specify the delimiter and the encoding. By default, the encoding is set to UTF-8.

By default, all values get converted to `int`, `float`, `bool` or otherwise `str`. The type casting can be disabled to speed up the reading time by setting the `cast_types` parameter to `False`.

The examples below use this CSV file:

{% code title="data.csv" fullWidth="false" %}

```csv
name;age
Joe;20
Jane;30
John;78
```

{% endcode %}

```python
from pystreamapi import Stream
from pystreamapi.loaders import csv

Stream.of(csv("path/to/data.csv", delimiter=";", encoding="us-ascii")) \
    .map(lambda x: x.name) \
    .for_each(print) # "Joe", "Jane", "John"
```

If you want to disable type conversion, you can use the loader like this:

```python
from pystreamapi import Stream
from pystreamapi.loaders import csv

Stream.of(csv("path/to/data.csv", cast_types=False, delimiter=";")) \
    .map(lambda x: x.age) \
    .for_each(print) # "20", "30", "78"
```

### JSON loader

In order to load the data from a JSON file, you can use the `json` loader.

The loader isn't included in the core version of pystreamapi. You can install it using the following command:

```bash
pip install 'streams.py[json_loader]'
```

:tada: Now you can use the loader as described below!

You can read data either from a JSON file or a string containing JSON. If you read from a string you have to set the `read_from_src` parameter to `True`.

By default, all values get converted to `int`, `float`, `bool` or otherwise `str`.

The example below uses this JSON file:

{% code title="data.json" fullWidth="false" %}

```json
[
  {
    "name": "Joe",
    "age": 20
  },
  {
    "name": "Jane",
    "age": 30
  },
  {
    "name": "John",
    "age": 78
  }
]
```

{% endcode %}

```python
from pystreamapi import Stream
from pystreamapi.loaders import json

Stream.of(json("path/to/data.json")) \
    .map(lambda x: x.name) \
    .for_each(print) # "Joe", "Jane", "John"
```

If you want to pass the JSON directly as a string, you can do it like that:

```python
from pystreamapi import Stream
from pystreamapi.loaders import json

Stream.of(json("[{\"name\":\"Joe\",\"age\":20},{\"name\":\"Jane\",\"age\":30}]", 
               read_from_src=True)) \
    .map(lambda x: x.age) \
    .for_each(print)  # 20, 30
```

### XML loader

In order to load the data from an XML file, you can use the `xml` loader.

```python
def xml(src: str, read_from_src=False, retrieve_children=True, cast_types=True,
        encoding="utf-8")
```

The loader isn't included in the core version of pystreamapi. You can install it using the following command:

```bash
pip install 'streams.py[xml_loader]'
```

:tada: Now you can use the loader as described below!

You just need the file's path, and you can optionally specify the encoding. By default, the encoding is set to UTF-8.

You can read data either from an XML file or a string containing XML. If you read from a string, you have to set the `read_from_src` parameter to `True`.

By default, all values get converted to `int`, `float`, `bool` or otherwise `str`. The type casting can be disabled to speed up the reading time by setting the `cast_types` parameter to `False`.

The XML loader directly retrieves the children nodes from the XML's root. By setting the `retrieve_children` parameter to `False` you disable this feature and your stream will only consist of one object containing the whole XML tree.

The examples below use this XML file:

{% code title="data.xml" %}

```xml
<employees>
    <employee>
        <name>John Doe</name>
        <cars>
            <car>Audi</car>
        </cars>
    </employee>
    <employee>
        <name>Alice Smith</name>
        <cars>
            <car>Volvo</car>
            <car>Volkswagen</car>
        </cars>
    </employee>
    <founder>
        <name>Martini Boss</name>
        <cars>
            <car>Bugatti</car>
            <car>Mercedes</car>
        </cars>
    </founder>
</employees>
```

{% endcode %}

Here you can see a few examples illustrating how to access different nodes.

```python
from pystreamapi import Stream
from pystreamapi.loaders import xml

Stream.of(xml("path/to/data.xml")) \
    .map(lambda x: x.name) \
    .for_each(print)  # John Doe, Alice Smith, Martini Boss
    
Stream.of(xml("path/to/data.xml")) \
      .map(lambda x: x.cars.car) \
      .for_each(print)  # 'Audi', ['Volvo', 'Volkswagen'], ['Bugatti', 'Mercedes']

Stream.of(xml("path/to/data.xml")) \
      .map(lambda x: type(x).__name__) \
      .for_each(print)  # employee, employee, founder
```

If you disable child retrieving, you have to map the object's children manually:

{% code title="data.xml" fullWidth="false" %}

```xml
<employees>
    <employee>
        <name>John Doe</name>
    </employee>
    <employee>
        <name>Alice Smith</name>
    </employee>
</employees>
```

{% endcode %}

<pre class="language-python"><code class="lang-python"><strong>from pystreamapi import Stream
</strong>from pystreamapi.loaders import xml

Stream.of(xml("data.xml", retrieve_children=False)) \
    .map(lambda x: x.employee) \
    .flat_map(lambda x: Stream.of(x)) \
    .map(lambda x: x.name) \
    .for_each(print)  # John Doe, Alice Smith
</code></pre>

### YAML loader

In order to load the data from a YAML file, you can use the `yaml` loader.

You can read data either from a YAML file or a string containing YAML. If you read from a string you have to set the `read_from_src` parameter to `True`.

By default, all values get converted to `int`, `float`, `bool` or otherwise `str`.

The example below uses this YAML file:

{% code title="data.yaml" fullWidth="false" %}

```yaml
- name: Joe
  age: 20
- name: Jane
  age: 30
- name: John
  age: 78
```

{% endcode %}

```python
from pystreamapi import Stream
from pystreamapi.loaders import yaml

Stream.of(yaml("path/to/data.yaml")) \
    .map(lambda x: x.name) \
    .for_each(print) # "Joe", "Jane", "John"
```

If you want to pass the YAML directly as a string, you can do it like that:

```python
from pystreamapi import Stream
from pystreamapi.loaders import yaml

Stream.of(yaml("- name: Joe\n  age: 20\n- name: Jane\n  age: 30", 
               read_from_src=True)) \
    .map(lambda x: x.age) \
    .for_each(print)  # 20, 30
```

### TOML loader

In order to load the data from a TOML file, you can use the `toml` loader.

The `toml` loader is included in the core version of pystreamapi — no extra install is needed.

The loader reads the entire TOML document and yields it as a single namedtuple, so attributes map directly to top-level keys.

You can read data either from a TOML file or a string containing TOML. If you read from a string you have to set the `read_from_src` parameter to `True`.

The example below uses this TOML file:

{% code title="config.toml" fullWidth="false" %}

```toml
[server]
host = "localhost"
port = 8080

[database]
name = "mydb"
```

{% endcode %}

```python
from pystreamapi import Stream
from pystreamapi.loaders import toml

Stream.of(toml("path/to/config.toml")) \
    .map(lambda x: x.server.host) \
    .for_each(print)  # "localhost"
```

If you want to pass the TOML directly as a string, you can do it like that:

```python
from pystreamapi import Stream
from pystreamapi.loaders import toml

Stream.of(toml("[server]\nhost = \"localhost\"\nport = 8080", 
               read_from_src=True)) \
    .map(lambda x: x.server.port) \
    .for_each(print)  # 8080
```


# Examples

More complex examples

Here are two complex examples demonstrating the power of PyStreamAPI

### **Get all numbers from list of different types. Use parallelization.**

```python
Stream.parallel_of([" ", '3', None, "2", 1, ""]) \
    .filter(lambda x: x is not None) \
    .map(str) \
    .map(lambda x: x.strip()) \
    .filter(lambda x: len(x) > 0) \
    .map(int) \
    .sorted()\
    .for_each(print) # 1 2 3
```

### **Generate a Stream of 10 Fibonacci numbers**

```python
def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

Stream.of(fib()) \
    .limit(10) \
    .for_each(print) # 0 1 1 2 3 5 8 13 21 34
```


# Performance

Note that parallel Streams are not always faster than sequential Streams. Especially when the number of elements is small, we can expect sequential Streams to be faster.

The operation that profits most from parallelization is `filter()`

{% hint style="info" %}
If you are not sure wich implementation to choose, let the builder decide:

```python
Stream.of(range(1000))
```

{% endhint %}


# Contribute

### Bug Reports

Bug reports can be submitted in GitHub's [issue tracker](https://github.com/PickwickSoft/pystreamapi/issues).

### Contributing

Contributions are welcome! Please submit a pull request or open an issue.


