-
-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add messages implementation for python (#165)
* [python] Add messages implementation for python * [python] Review fixes * Fixup property type definitions * Fixup property descriptions * Descriptions inlined where possible * Property descriptions are placed after properties per se * Remove redundant double-quotes at type definitions * Split enums and model templates * Simplify gh-action test matrix * Fixup empty project.toml settings * Update python/pyproject.toml Co-authored-by: Luke Hill <20105237+luke-hill@users.noreply.github.com> * Update CHANGELOG.md --------- Co-authored-by: Luke Hill <20105237+luke-hill@users.noreply.github.com> Co-authored-by: David Goss <david@davidgoss.co>
- Loading branch information
1 parent
f375dce
commit 4ed7f02
Showing
23 changed files
with
2,033 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
name: Release Python | ||
|
||
on: | ||
push: | ||
branches: [release/*] | ||
|
||
jobs: | ||
release: | ||
name: Release | ||
runs-on: ubuntu-latest | ||
environment: Release | ||
permissions: | ||
id-token: write | ||
defaults: | ||
run: | ||
working-directory: python | ||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v4 | ||
|
||
- uses: cucumber/action-publish-pypi@v3.0.0 | ||
with: | ||
working-directory: "python" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
--- | ||
name: test-python | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
- renovate/** | ||
pull_request: | ||
branches: | ||
- main | ||
workflow_dispatch: | ||
|
||
jobs: | ||
build: | ||
|
||
runs-on: ${{ matrix.os }} | ||
strategy: | ||
matrix: | ||
include: | ||
# Test latest python on Windows / macOS | ||
- { os: 'windows-latest', python-version: '3.13' } | ||
- { os: 'macos-latest', python-version: '3.13' } | ||
os: ['ubuntu-latest'] | ||
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', 'pypy3.9', 'pypy3.10'] | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Set up Python ${{ matrix.python-version }} | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
- name: Install dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install -U setuptools | ||
pip install tox tox-gh-actions codecov | ||
- name: Test with tox | ||
working-directory: ./python | ||
run: | | ||
tox | ||
- name: Gather codecov report | ||
working-directory: ./python | ||
run: | | ||
codecov |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# See https://pre-commit.com for more information | ||
# See https://pre-commit.com/hooks.html for more hooks | ||
--- | ||
files: ^python/ | ||
exclude: .*python/src/cucumber_messages/_messages\.py | ||
repos: | ||
- repo: https://github.com/psf/black | ||
rev: 24.10.0 | ||
hooks: | ||
- id: black | ||
args: | ||
- "python/src" | ||
- "python/tests" | ||
- repo: https://github.com/pycqa/isort | ||
rev: 5.13.2 | ||
hooks: | ||
- id: isort | ||
- repo: https://github.com/pre-commit/pre-commit-hooks | ||
rev: v5.0.0 | ||
hooks: | ||
- id: trailing-whitespace | ||
- id: end-of-file-fixer | ||
- id: check-added-large-files | ||
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks | ||
rev: v2.14.0 | ||
hooks: | ||
- id: pretty-format-toml | ||
args: [--autofix] | ||
- repo: https://github.com/asottile/pyupgrade | ||
rev: v3.19.1 | ||
hooks: | ||
- id: pyupgrade | ||
args: ["--py39-plus"] | ||
- repo: https://github.com/pre-commit/mirrors-mypy | ||
rev: v1.13.0 | ||
hooks: | ||
- id: mypy | ||
additional_dependencies: [types-setuptools, types-certifi] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
# frozen_string_literal: true | ||
|
||
module Generator | ||
class Python < Base | ||
def format_enum_value(value) | ||
value.downcase.gsub(/[.\/+\s-]/, '_') | ||
end | ||
|
||
def get_sorted_properties(definition) | ||
required_fields = definition['required'] || [] | ||
definition['properties'].sort_by do |name, *| | ||
[required_fields.include?(name) ? 0 : 1, name] | ||
end | ||
end | ||
|
||
def format_property(parent_type_name, property_name, property, required_fields) | ||
snake_name = property_name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') | ||
.gsub(/([a-z\d])([A-Z])/, '\1_\2') | ||
.downcase | ||
|
||
property_type = get_property_type(parent_type_name, property_name, property) | ||
is_required = required_fields.include?(property_name) | ||
|
||
property_description = if property['description'] && !property['description'].include?("\n") | ||
" # #{property['description']}" | ||
else | ||
'' | ||
end | ||
if is_required | ||
"#{snake_name}: #{property_type}#{property_description}" | ||
else | ||
"#{snake_name}: Optional[#{property_type}] = None#{property_description}" | ||
end | ||
end | ||
|
||
def get_property_type(parent_type_name, property_name, property) | ||
type = type_for(parent_type_name, property_name, property) | ||
type.match?(/\A[A-Z]/) ? class_name(type) : type | ||
end | ||
|
||
def array_type_for(type_name) | ||
inner_type = if language_translations_for_data_types.values.include?(type_name) | ||
type_name # Keep primitive types as is | ||
else | ||
class_name(type_name) # CamelCase for complex types | ||
end | ||
inner_type | ||
end | ||
|
||
def format_description(raw_description, indent_string: ' ') | ||
return '""" """' if raw_description.nil? | ||
|
||
lines = raw_description.split("\n").map { |line| | ||
if line.strip.empty? | ||
"" | ||
else | ||
"#{indent_string}#{line.rstrip}" | ||
end | ||
} | ||
|
||
%("""\n#{lines.join("\n")}\n#{indent_string}""") | ||
end | ||
|
||
def language_translations_for_data_types | ||
{ | ||
'integer' => 'int', | ||
'string' => 'str', | ||
'boolean' => 'bool', | ||
'array' => 'list' | ||
} | ||
end | ||
|
||
private | ||
|
||
def default_value(parent_type_name, property_name, property) | ||
if property['type'] == 'string' | ||
default_value_for_string(parent_type_name, property_name, property) | ||
elsif property['type'] == 'integer' | ||
'0' | ||
elsif property['type'] == 'boolean' | ||
'False' | ||
elsif property['type'] == 'array' | ||
'[]' | ||
elsif property['$ref'] | ||
"#{class_name(type_for(parent_type_name, nil, property))}()" | ||
else | ||
'None' | ||
end | ||
end | ||
|
||
def default_value_for_string(parent_type_name, property_name, property) | ||
if property['enum'] | ||
enum_type_name = type_for(parent_type_name, property_name, property) | ||
"#{class_name(enum_type_name)}.#{enum_constant(property['enum'][0])}" | ||
else | ||
'""' | ||
end | ||
end | ||
|
||
def type_for(parent_type_name, property_name, property) | ||
if property['$ref'] | ||
property_type_from_ref(property['$ref']) | ||
elsif property['type'] | ||
property_type_from_type(parent_type_name, property_name, property, type: property['type']) | ||
else | ||
raise "Property #{property_name} did not define 'type' or '$ref'" | ||
end | ||
end | ||
|
||
def property_type_from_type(parent_type_name, property_name, property, type:) | ||
if type == 'array' | ||
type = type_for(parent_type_name, nil, property['items']) | ||
inner_type = array_type_for(type) | ||
"list[#{inner_type}]" | ||
elsif property['enum'] | ||
enum_name(parent_type_name, property_name, property['enum']) | ||
else | ||
language_translations_for_data_types.fetch(type) | ||
end | ||
end | ||
|
||
def enum_constant(value) | ||
value.gsub(/[.\/+]/, '_').downcase | ||
end | ||
|
||
def enum_name(parent_type_name, property_name, enum) | ||
"#{class_name(parent_type_name)}#{capitalize(property_name)}".tap do |name| | ||
@enum_set.add({ name: name, values: enum }) | ||
end | ||
end | ||
|
||
def property_type_from_ref(ref) | ||
class_name(ref) | ||
end | ||
|
||
def class_name(ref) | ||
return ref if language_translations_for_data_types.values.include?(ref) | ||
|
||
# Remove .json extension if present | ||
name = ref.sub(/\.json$/, '') | ||
# Get the basename without path | ||
name = File.basename(name) | ||
# Convert each word to proper case, handling camelCase and snake_case | ||
parts = name.gsub(/[._-]/, '_').split('_').map do |part| | ||
# Split by any existing camelCase | ||
subparts = part.scan(/[A-Z][a-z]*|[a-z]+/) | ||
subparts.map(&:capitalize).join | ||
end | ||
# Join all parts to create final CamelCase name | ||
parts.join | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# This code was generated using the code generator from cucumber-messages. | ||
# Manual changes will be lost if the code is regenerated. | ||
# Generator: cucumber-messages-python | ||
|
||
from enum import Enum | ||
|
||
|
||
<%- @enums.each_with_index do |enum, index| -%> | ||
class <%= enum[:name] %>(Enum): | ||
<%- enum[:values].each do |value| -%> | ||
<%= format_enum_value(value) %> = "<%= value %>" | ||
<%- end -%> | ||
<%- if index < @enums.length - 1 -%> | ||
|
||
|
||
<%- end -%> | ||
<%- end -%> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# This code was generated using the code generator from cucumber-messages. | ||
# Manual changes will be lost if the code is regenerated. | ||
# Generator: cucumber-messages-python | ||
|
||
from __future__ import annotations | ||
from dataclasses import dataclass | ||
from typing import Optional | ||
|
||
from ._message_enums import * | ||
|
||
<%- @schemas.each_with_index do |schema_pair, index| -%> | ||
<%- key, definition = schema_pair -%> | ||
@dataclass | ||
class <%= class_name(key) %>: | ||
<%- if definition['description'] -%> | ||
<%= format_description(definition['description']) %> | ||
<%- end -%> | ||
<%- if definition['properties'].any? -%> | ||
<%- required_fields = definition['required'] || [] -%> | ||
<%- get_sorted_properties(definition).each do |property_name, property| -%> | ||
<%= format_property(key, property_name, property, required_fields) %> | ||
<%- if property['description'] && property['description'].include?("\n") -%> | ||
<%= format_description(property['description']) %> | ||
|
||
<%- end -%> | ||
<%- end -%> | ||
<%- else -%> | ||
pass | ||
<%- end -%> | ||
<%- if index < @schemas.length - 1 -%> | ||
|
||
|
||
<%- end -%> | ||
<%- end -%> |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
*.rej | ||
*.py[cod] | ||
/.env | ||
*.orig | ||
**/__pycache__ | ||
|
||
# C extensions | ||
*.so | ||
|
||
# Packages | ||
*.egg | ||
*.egg-info | ||
dist | ||
build | ||
_build | ||
eggs | ||
parts | ||
bin | ||
var | ||
sdist | ||
develop-eggs | ||
.installed.cfg | ||
lib | ||
lib64 | ||
|
||
# Installer logs | ||
pip-log.txt | ||
|
||
# Unit test / coverage reports | ||
.coverage | ||
.tox | ||
nosetests.xml | ||
|
||
# Translations | ||
*.mo | ||
|
||
# Mr Developer | ||
.mr.developer.cfg | ||
.project | ||
.pydevproject | ||
.pytest_cache | ||
.ropeproject | ||
|
||
# Sublime | ||
/*.sublime-* | ||
|
||
#PyCharm | ||
/.idea | ||
|
||
# virtualenv | ||
/.Python | ||
/lib | ||
/include | ||
/share | ||
/local |
Oops, something went wrong.