Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new standards for deprecating APIs #1365

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,12 @@ Home page: https://hive.apache.org/
License: https://www.apache.org/licenses/LICENSE-2.0

--------------------------------------------------------------------------------

This product includes code from Conda.

* Uses deprecations.py file that's part of conda project in conda/conda/
* Uses test_deprecations.py file that's part of conda project in conda/tests/

Copyright: 2012 Anaconda, Inc.
Home page: https://conda.io/
License: https://opensource.org/licenses/BSD-3-Clause
160 changes: 141 additions & 19 deletions mkdocs/docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,43 +159,165 @@ Below are the formalized conventions that we adhere to in the PyIceberg project.

It is important to keep the Python public API compatible across versions. The Python official [PEP-8](https://peps.python.org/pep-0008/) defines public methods as: _Public attributes should have no leading underscores_. This means not removing any methods without any notice, or removing or renaming any existing parameters. Adding new optional parameters is okay.

If you want to remove a method, please add a deprecation notice by annotating the function using `@deprecated`:
### Functions, Methods, Properties, and Classes

To deprecate functions, methods, or properties, use the `@deprecated` decorator. This indicates that the feature will be removed in a future release and notifies users about the deprecation timeline.

#### Basic Example

```python
from pyiceberg.utils._deprecations import deprecated


@deprecated("1.5.0", "2.0.0")
def old_function():
pass


old_function()
```

This will warn:

```text
some_file.py:9: DeprecationWarning: __main__.old_function is deprecated and will be removed in 2.0.0.
```

#### Adding a Recommendation

Optionally, include a recommendation to guide users toward an alternative feature:

```python
from pyiceberg.utils._deprecations import deprecated


@deprecated("1.5.0", "2.0.0", addendum="Use `new_function` instead.")
def old_function():
pass


old_function()
```

This will warn:

```text
some_file.py:9: DeprecationWarning: __main__.old_function is deprecated and will be removed in 2.0.0. Use `new_function` instead.
```

### Keyword Arguments

To deprecate or rename keyword arguments, use the `@deprecated.argument` decorator.

#### Deprecating a Keyword Argument

```python
from pyiceberg.utils.deprecated import deprecated
from pyiceberg.utils._deprecations import deprecated


@deprecated(
deprecated_in="0.1.0",
removed_in="0.2.0",
help_message="Please use load_something_else() instead",
)
def load_something():
@deprecated.argument("1.5.0", "2.0.0", "old_arg")
def my_function(*, old_arg=True):
pass


my_function(old_arg=False)
```

Which will warn:
This will warn:

```text
Call to load_something, deprecated in 0.1.0, will be removed in 0.2.0. Please use load_something_else() instead.
some_file.py:9: DeprecationWarning: __main__.my_function(old_arg) is deprecated and will be removed in 2.0.0.
```

If you want to remove a property or notify about a behavior change, please add a deprecation notice by calling the deprecation_message function:
#### Renaming a Keyword Argument

```python
from pyiceberg.utils.deprecated import deprecation_message
from pyiceberg.utils._deprecations import deprecated


@deprecated.argument("1.5.0", "2.0.0", "old_arg", rename="new_arg")
def my_function(*, new_arg=True):
pass


my_function(old_arg=False)
```

This will warn:

```text
some_file.py:9: DeprecationWarning: __main__.my_function(old_arg) is deprecated and will be removed in 2.0.0. Use 'new_arg' instead.
```

### Constants and Enums

To deprecate constants or enums, use the `deprecated.constant` function.

#### Deprecating a Constant

```python
# some_file.py
from pyiceberg.utils._deprecations import deprecated

deprecated.constant("1.5.0", "2.0.0", "OLD_CONSTANT", 42)

# deprecated_constant.py
from some_file import OLD_CONSTANT

OLD_CONSTANT
```

This will warn:

```text
deprecated_constant.py:3: DeprecationWarning: some_file.OLD_CONSTANT is deprecated and will be removed in 2.0.0.
```

#### Deprecating an Enum

```python
# some_file.py
from enum import Enum
from pyiceberg.utils._deprecations import deprecated

class MyEnum(Enum):
OLD_VALUE = 42

deprecated.constant("1.5.0", "2.0.0", "MyEnum", MyEnum)

del MyEnum

# deprecated_enum.py
from some_file import MyEnum
```

This will warn:

```text
deprecated_constant.py:1: DeprecationWarning: some_file.MyEnum is deprecated and will be removed in 2.0.0.
```

### Topics

For deprecations that do not fit into the above categories, use the `deprecated.topic` function.

```python
from pyiceberg.utils._deprecations import deprecated


def some_function():
# some logic

if condition:
deprecated.topic("1.5.0", "2.0.0", prefix="The behaviour xyz", addendum="Do something else.")

deprecation_message(
deprecated_in="0.1.0",
removed_in="0.2.0",
help_message="The old_property is deprecated. Please use the something_else property instead.",
)
# more logic
```

Which will warn:
This will warn:

```text
Deprecated in 0.1.0, will be removed in 0.2.0. The old_property is deprecated. Please use the something_else property instead.
some_file.py:8: DeprecationWarning: The behaviour xyz is deprecated and will be removed in 2.0.0. Do something else.
```

## Type annotations
Expand Down
14 changes: 3 additions & 11 deletions mkdocs/docs/how-to-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,13 @@ For example, the API with the following deprecation tag should be removed when p
```python

@deprecated(
deprecated_in="0.1.0",
removed_in="0.2.0",
deprecate_in="0.1.0",
remove_in="0.2.0",
help_message="Please use load_something_else() instead",
)
```

We also have the `deprecation_message` function. We need to change the behavior according to what is noted in the message of that deprecation.

```python
deprecation_message(
deprecated_in="0.1.0",
removed_in="0.2.0",
help_message="The old_property is deprecated. Please use the something_else property instead.",
)
```
We also have other deprecation strategies, so we recommend identifying all code references using the deprecation module (`pyiceberg.utils._deprecations`) and removing them before making a release.

### Update Library Version

Expand Down
26 changes: 14 additions & 12 deletions pyiceberg/catalog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@
Properties,
RecursiveDict,
)
from pyiceberg.utils._deprecations import deprecated
from pyiceberg.utils.config import Config, merge_config
from pyiceberg.utils.deprecated import deprecated, deprecation_message

if TYPE_CHECKING:
import pyarrow as pa
Expand Down Expand Up @@ -631,9 +631,9 @@ def drop_view(self, identifier: Union[str, Identifier]) -> None:
"""

@deprecated(
deprecated_in="0.8.0",
removed_in="0.9.0",
help_message="Support for parsing catalog level identifier in Catalog identifiers is deprecated. Please refer to the table using only its namespace and its table name.",
deprecate_in="0.8.0",
remove_in="0.9.0",
addendum="Please refer to the table using only its namespace and its table name.",
)
def identifier_to_tuple_without_catalog(self, identifier: Union[str, Identifier]) -> Identifier:
"""Convert an identifier to a tuple and drop this catalog's name from the first element.
Expand All @@ -660,10 +660,11 @@ def _identifier_to_tuple_without_catalog(self, identifier: Union[str, Identifier
"""
identifier_tuple = Catalog.identifier_to_tuple(identifier)
if len(identifier_tuple) >= 3 and identifier_tuple[0] == self.name:
deprecation_message(
deprecated_in="0.8.0",
removed_in="0.9.0",
help_message="Support for parsing catalog level identifier in Catalog identifiers is deprecated. Please refer to the table using only its namespace and its table name.",
deprecated.topic(
deprecate_in="0.8.0",
remove_in="0.9.0",
prefix="Support for parsing catalog level identifier in Catalog identifiers",
addendum="Please refer to the table using only its namespace and its table name.",
)
identifier_tuple = identifier_tuple[1:]
return identifier_tuple
Expand Down Expand Up @@ -782,10 +783,11 @@ def __init__(self, name: str, **properties: str):
super().__init__(name, **properties)

if self.properties.get(DEPRECATED_BOTOCORE_SESSION):
deprecation_message(
deprecated_in="0.8.0",
removed_in="0.9.0",
help_message=f"The property {DEPRECATED_BOTOCORE_SESSION} is deprecated and will be removed.",
deprecated.topic(
deprecate_in="0.8.0",
remove_in="0.9.0",
prefix=f"The property {DEPRECATED_BOTOCORE_SESSION}",
addendum="and will be removed.",
)

def create_table_transaction(
Expand Down
20 changes: 11 additions & 9 deletions pyiceberg/catalog/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
)
from pyiceberg.typedef import EMPTY_DICT, UTF8, IcebergBaseModel, Identifier, Properties
from pyiceberg.types import transform_dict_value_to_str
from pyiceberg.utils.deprecated import deprecation_message
from pyiceberg.utils._deprecations import deprecated
from pyiceberg.utils.properties import get_first_property_value, property_as_bool

if TYPE_CHECKING:
Expand Down Expand Up @@ -318,10 +318,11 @@ def url(self, endpoint: str, prefixed: bool = True, **kwargs: Any) -> str:
@property
def auth_url(self) -> str:
if self.properties.get(AUTH_URL):
deprecation_message(
deprecated_in="0.8.0",
removed_in="0.9.0",
help_message=f"The property {AUTH_URL} is deprecated. Please use {OAUTH2_SERVER_URI} instead",
deprecated.topic(
deprecate_in="0.8.0",
remove_in="0.9.0",
prefix=f"The property {AUTH_URL}",
addendum=f"Please use {OAUTH2_SERVER_URI} instead",
)

self._warn_oauth_tokens_deprecation()
Expand All @@ -338,10 +339,11 @@ def _warn_oauth_tokens_deprecation(self) -> None:
has_sigv4_enabled = property_as_bool(self.properties, SIGV4, False)

if not has_oauth_server_uri and (has_init_token or has_credential) and not has_sigv4_enabled:
deprecation_message(
deprecated_in="0.8.0",
removed_in="1.0.0",
help_message="Iceberg REST client is missing the OAuth2 server URI "
deprecated.topic(
deprecate_in="0.8.0",
remove_in="1.0.0",
prefix="Default OAuth2 endpoint",
addendum="Iceberg REST client is missing the OAuth2 server URI "
f"configuration and defaults to {self.uri}{Endpoints.get_token}. "
"This automatic fallback will be removed in a future Iceberg release."
f"It is recommended to configure the OAuth2 endpoint using the '{OAUTH2_SERVER_URI}'"
Expand Down
38 changes: 15 additions & 23 deletions pyiceberg/cli/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,24 @@
from pyiceberg.exceptions import NoSuchNamespaceError, NoSuchPropertyException, NoSuchTableError
from pyiceberg.table import TableProperties
from pyiceberg.table.refs import SnapshotRef
from pyiceberg.utils.deprecated import deprecated
from pyiceberg.utils._deprecations import deprecated
from pyiceberg.utils.properties import property_as_int

deprecated.constant(
deprecate_in="0.8.0",
remove_in="0.9.0",
addendum="Use TableProperties.MAX_SNAPSHOT_AGE_MS_DEFAULT instead.",
constant="DEFAULT_MAX_SNAPSHOT_AGE_MS",
value=TableProperties.MAX_SNAPSHOT_AGE_MS_DEFAULT,
)

class DeprecatedConstants:
@property
@deprecated(
deprecated_in="0.8.0",
removed_in="0.9.0",
help_message="DEFAULT_MAX_SNAPSHOT_AGE_MS is deprecated. Use TableProperties.MAX_SNAPSHOT_AGE_MS_DEFAULT instead.",
)
def DEFAULT_MAX_SNAPSHOT_AGE_MS(self) -> int:
return 432000000

@property
@deprecated(
deprecated_in="0.8.0",
removed_in="0.9.0",
help_message="DEFAULT_MIN_SNAPSHOTS_TO_KEEP is deprecated. Use TableProperties.MIN_SNAPSHOTS_TO_KEEP_DEFAULT instead.",
)
def DEFAULT_MIN_SNAPSHOTS_TO_KEEP(self) -> int:
return 1


DEFAULT_MIN_SNAPSHOTS_TO_KEEP = DeprecatedConstants().DEFAULT_MIN_SNAPSHOTS_TO_KEEP
DEFAULT_MAX_SNAPSHOT_AGE_MS = DeprecatedConstants().DEFAULT_MAX_SNAPSHOT_AGE_MS
deprecated.constant(
deprecate_in="0.8.0",
remove_in="0.9.0",
addendum="Use TableProperties.MIN_SNAPSHOTS_TO_KEEP_DEFAULT instead.",
constant="DEFAULT_MIN_SNAPSHOTS_TO_KEEP",
value=TableProperties.MIN_SNAPSHOTS_TO_KEEP_DEFAULT,
)


def catch_exception() -> Callable: # type: ignore
Expand Down
11 changes: 6 additions & 5 deletions pyiceberg/expressions/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
)
from pyiceberg.typedef import L
from pyiceberg.types import strtobool
from pyiceberg.utils.deprecated import deprecation_message
from pyiceberg.utils._deprecations import deprecated

ParserElement.enablePackrat()

Expand All @@ -90,10 +90,11 @@
@column.set_parse_action
def _(result: ParseResults) -> Reference:
if len(result.column) > 1:
deprecation_message(
deprecated_in="0.8.0",
removed_in="0.9.0",
help_message="Parsing expressions with table name is deprecated. Only provide field names in the row_filter.",
deprecated.topic(
deprecate_in="0.8.0",
remove_in="0.9.0",
prefix="Parsing expressions with table name",
addendum="Only provide field names in the row_filter.",
)
# TODO: Once this is removed, we will no longer take just the last index of parsed column result
# And introduce support for parsing filter expressions with nested fields.
Expand Down
Loading
Loading