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 Python 3.10 support (close #254) #260

Merged
merged 5 commits into from
Dec 15, 2021

Conversation

matus-tomlein
Copy link
Contributor

This PR addresses issue #254 and adds support for Python 3.10 by dropping the pycontracts library (not updated for some time and incompatible with 3.10 at this time) and adding some other features that require support for Python 2 to be dropped.

The full list of changes is:

  • Remove pycontracts library that wasn't compatible with Python 3.10
  • Add Python type hints to all function arguments and return values
  • Remove support for Python 2 due to the type hints
  • Add custom contracts (e.g., non_empty_string) evaluated at the start of functions
  • Run through Flake8 linter and correct style
  • Add Dockerfile for running tests under all supported Python versions

Note:

  • Python type hints are not enforced during runtime (they can be checked using mypy static type checking or through IDEs such as PyCharm) so some types of issues that previously raised errors no longer do so
    • in particular, this is the case for instance type matching, I didn't implement contracts for checking instance types – I don't think this is a standard behaviour in Python and IDEs should help with these types of errors using the type hints
    • I tried to add custom contracts for non-instance-type checks such non-empty strings, values larger than 0, and non-empty lists
  • There is a potentially breaking change because we no longer raise ContractNotRespected error (from pycontracts) when contracts are violated but the standard ValueError

@coveralls
Copy link

coveralls commented Dec 9, 2021

Coverage Status

Coverage decreased (-0.7%) to 96.791% when pulling 02dbee3 on issue/254-python_310 into 8fc7f1e on master.

@paulboocock
Copy link
Contributor

I'm also assigning @adatzer to review this. I'll hopefully get to it too shortly but I'd love her input on this too given she has been the most recent maintainer on this tracker.

@snowplow snowplow deleted a comment from snowplowcla Dec 9, 2021
@paulboocock paulboocock removed the cla:no label Dec 9, 2021
* Remove pycontracts library that wasn't compatible with Python 3.10
* Add Python type hints to all function arguments and return values
* Remove support for Python 2 due to the type hints
* Add custom contracts (e.g., non_empty_string) evaluated at the start of functions
* Run through Flake8 linter and correct style
* Remove Python 2 from Github action build
Copy link
Contributor

@paulboocock paulboocock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good to me. I'll let @adatzer have the final say on approving this PR though.

Just a note that when preparing the 0.10.0 release, we should add a table to the README to make it clear what versions of the tracker support what versions of python (i.e. Python 2.7 is 0.9.x only, Python 3+ is 0.10.x+)

Copy link
Contributor

@adatzer adatzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!! Feels much cleaner and with less dependencies too! Just added few comments below.
Also, just noting this PR targets master, i guess it will go for a release branch eventually, right?
Finally, what do you think of removing the pycontracts's :type function comments too?

run-tests.sh Outdated

# pyenv install 3.10.0
if [ ! -e ~/.pyenv/versions/tracker310 ]; then
pyenv virtualenv 3.10.0 tracker310
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here, could we go straight for latest 3.10.1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I didn't there was 3.10.1 already, updated!

pyVersion = sys.version_info[0]
if pyVersion < 3:
if isinstance(x, basestring):
if isinstance(x, str):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment refers to lines 165-166: Can we now assume that pyVersion will not be < 3?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, we don't need that if anymore, removed it.


class EmitterProtocol(Protocol):
def input(self, payload: PayloadDict) -> None:
...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm on the ..., is it just used like pass here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the ellipsis can have more uses (in slices), but they are commonly used instead of pass. It just looks a bit nicer.

@@ -12,7 +12,7 @@ jobs:

strategy:
matrix:
python-version: [2.7, 3.6, 3.7, 3.8, 3.9]
python-version: [3.6, 3.7, 3.8, 3.9]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add 3.10 as well for the tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, added 3.10 here as well. Just had to make it string, otherwise Github actions understood it as version 3.1.

node_name: FormNodeName,
value: Optional[str],
type_: Optional[str] = None,
element_classes: Optional[Union[List[str], Tuple[str]]] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here, for element_classes, the tuple in their previous contract was tuple(str,*). Would that map to Tuple[str, Any]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Changed the tuple to Tuple[str, Any] and moved it to typing.py since it's a bit too long.

def __exit__(self, type, value, traceback):
enable_all()
def __enter__(self) -> None:
disable_contracts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - was this meant as a function call? i.e. disable_contracts()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really good catch! It actually means that the ContractsDisabled was useless since all the tests passed without it doing anything. I added the function call and removed ContractsDisabled from it where it wasn't necessary anymore. Also added a couple of tests that make use of it.

disable_contracts

def __exit__(self, type: Any, value: Any, traceback: Any) -> None:
enable_contracts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here, to call enable_contracts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented above, thanks for this!

DEFAULT_PLATFORM = "pc"

new_contract("subject", lambda x: isinstance(x, Subject))
SupportedPlatform = Literal["pc", "tv", "mob", "cnsl", "iot", "web", "srv", "app"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - would it make sense for this type to be defined in snowplow_tracker.typing as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, moved to typing.py.

Dockerfile Outdated
ENV PYENV_ROOT $HOME/.pyenv
ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH

RUN pyenv install 3.5.10 && pyenv install 3.6.14 && pyenv install 3.7.11 && pyenv install 3.8.11 && pyenv install 3.9.6 && pyenv install 3.10.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we also go straight for 3.10.1 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, updated to 3.10.1.


new_contract("function", lambda x: hasattr(x, "__call__"))
SuccessCallback = Callable[[PayloadDictList], None]
FailureCallback = Callable[[int, PayloadDictList], None]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - would it make sense for these types to be defined in snowplow_tracker.typing as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, moved all of these types to typing.py.

@matus-tomlein
Copy link
Contributor Author

Thanks a lot @adatzer for the thorough review! Those were really useful comments and also uncovered a few important problems.

Not sure about removing the :type pycontracts comments – they still have some value in documenting the types although now the type documentation is duplicated in the type hints too... Do you know if they are used when generating any API docs?

@matus-tomlein matus-tomlein changed the base branch from master to release/0.10.0 December 15, 2021 13:13
Copy link
Contributor

@adatzer adatzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really happy to approve from my side!
Concerning the pycontracts' comments, they are not used to generate API docs as far as i know. They do indeed add some value, so no objection from me for them to stay.

@matus-tomlein matus-tomlein merged commit 7cc11db into release/0.10.0 Dec 15, 2021
cpnat added a commit to kolonialno/snowplow-python-tracker that referenced this pull request Apr 5, 2022
* Fix pycontracts incompatibility with pyparsing v3 (closes snowplow#255)

* Update python versions in run-tests script (closes snowplow#256)

* Prepare for release

* Add Python 3.10 support (close snowplow#254)

PR snowplow#260

* Remove pycontracts library that wasn't compatible with Python 3.10
* Add Python type hints to all function arguments and return values
* Remove support for Python 2 due to the type hints
* Add custom contracts (e.g., non_empty_string) evaluated at the start of functions
* Run through Flake8 linter and correct style
* Remove Python 2 from Github action build
* Add Dockerfile for running tests under all supported Python versions

* Add configurable timeout for HTTP requests (close snowplow#258)

PR snowplow#259

* Prepare for release

* Increment version

Co-authored-by: adatzer <ada@snowplowanalytics.com>
Co-authored-by: Matúš Tomlein <matus.tomlein@gmail.com>
Co-authored-by: Deyan Deyanov <deyan@insurify.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants