Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .github/workflows/generate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ jobs:
run: go run . samples --sdk-version-file ../sumup/_version.py --out /tmp/sumup-py-code-samples.json ../openapi.json
working-directory: codegen

- name: Format
run: uv run ruff format

- name: Auto-fix
run: uv run ruff check --fix

- name: Format
run: uv run ruff format

- name: Create GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ import os

from sumup import AsyncSumup


async def main():
client = AsyncSumup(api_key="sup_sk_MvxmLOl0...")

# Get merchant profile
merchant = await client.merchants.get(merchant_code=os.environ["SUMUP_MERCHANT_CODE"])
print(f"Merchant: {merchant.merchant_code}")


asyncio.run(main())
```

Expand Down
18 changes: 15 additions & 3 deletions codegen/pkg/builder/out.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,14 @@ func isGeneratedResourceDir(dir string) bool {
}

func (b *Builder) writeClientFile(fname string, tags []string) error {
f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.FileMode(0o755))
f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.FileMode(0o644))
if err != nil {
return fmt.Errorf("create %q: %w", fname, err)
}
if err := f.Chmod(0o644); err != nil {
_ = f.Close()
return fmt.Errorf("set permissions on %q: %w", fname, err)
}
defer func() {
_ = f.Close()
}()
Expand Down Expand Up @@ -351,10 +355,14 @@ func (b *Builder) writeClientFile(fname string, tags []string) error {
}

func (b *Builder) writeAPIVersionFile(fname string) error {
f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.FileMode(0o755))
f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.FileMode(0o644))
if err != nil {
return fmt.Errorf("create %q: %w", fname, err)
}
if err := f.Chmod(0o644); err != nil {
_ = f.Close()
return fmt.Errorf("set permissions on %q: %w", fname, err)
}
defer func() {
_ = f.Close()
}()
Expand All @@ -375,10 +383,14 @@ func openGeneratedFile(filename string) (*os.File, error) {
}

p := filepath.Join(cwd, filename)
f, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.FileMode(0o755))
f, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.FileMode(0o644))
if err != nil {
return nil, fmt.Errorf("create %q: %w", p, err)
}
if err := f.Chmod(0o644); err != nil {
_ = f.Close()
return nil, fmt.Errorf("set permissions on %q: %w", p, err)
}

return f, nil
}
Expand Down
22 changes: 10 additions & 12 deletions codegen/pkg/builder/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,12 @@ func (o *OneOfDeclaration) String() string {
options = append(options, inputTypeName(option))
}
if o.RequestOnly {
fmt.Fprintf(buf, "%sInput = typing.Union[%s]", o.Name, strings.Join(options, ", "))
fmt.Fprintf(buf, "%sInput = %s", o.Name, strings.Join(options, " | "))
return buf.String()
}
fmt.Fprintf(buf, "%s = typing.Union[", o.Name)
fmt.Fprint(buf, strings.Join(o.Options, ", "))
fmt.Fprintf(buf, "]\n")
fmt.Fprintf(buf, "%s = %s\n", o.Name, strings.Join(o.Options, " | "))
if o.GenerateInput {
fmt.Fprintf(buf, "%sInput = typing.Union[%s]", o.Name, strings.Join(options, ", "))
fmt.Fprintf(buf, "%sInput = %s", o.Name, strings.Join(options, " | "))
}
return buf.String()
}
Expand All @@ -179,12 +177,12 @@ func (p *Property) String() string {
if useAlias {
aliasChoices := fmt.Sprintf("pydantic.AliasChoices(%q, %q)", alias, fieldName)
if p.Optional {
fmt.Fprintf(buf, "%s: typing.Optional[%s] = pydantic.Field(default=None, serialization_alias=%q, validation_alias=%s)\n", fieldName, p.Type, alias, aliasChoices)
fmt.Fprintf(buf, "%s: %s | None = pydantic.Field(default=None, serialization_alias=%q, validation_alias=%s)\n", fieldName, p.Type, alias, aliasChoices)
} else {
fmt.Fprintf(buf, "%s: %s = pydantic.Field(serialization_alias=%q, validation_alias=%s)\n", fieldName, p.Type, alias, aliasChoices)
}
} else if p.Optional {
fmt.Fprintf(buf, "%s: typing.Optional[%s] = None\n", fieldName, p.Type)
fmt.Fprintf(buf, "%s: %s | None = None\n", fieldName, p.Type)
} else {
fmt.Fprintf(buf, "%s: %s\n", fieldName, p.Type)
}
Expand All @@ -210,9 +208,9 @@ func (p Property) MethodParameterString(allowNone bool) string {
typeName := p.MethodParameterType()
if p.Optional {
if allowNone {
return fmt.Sprintf("%s: typing.Union[%s, None, NotGivenType] = NOT_GIVEN", p.FieldName(), typeName)
return fmt.Sprintf("%s: %s | None | NotGivenType = NOT_GIVEN", p.FieldName(), typeName)
}
return fmt.Sprintf("%s: typing.Union[%s, NotGivenType] = NOT_GIVEN", p.FieldName(), typeName)
return fmt.Sprintf("%s: %s | NotGivenType = NOT_GIVEN", p.FieldName(), typeName)
}

return fmt.Sprintf("%s: %s", p.FieldName(), typeName)
Expand Down Expand Up @@ -252,7 +250,7 @@ func (e *EnumDeclaration[E]) String() string {
for _, v := range e.Values {
values = append(values, fmt.Sprintf("%#v", v))
}
alias := fmt.Sprintf("typing.Union[typing.Literal[%s], %s]", strings.Join(values, ", "), pythonEnumBaseType(e.Type))
alias := fmt.Sprintf("typing.Literal[%s] | %s", strings.Join(values, ", "), pythonEnumBaseType(e.Type))
if e.RequestOnly {
return fmt.Sprintf("%sInput = %s\n", e.Name, alias)
}
Expand Down Expand Up @@ -319,9 +317,9 @@ func inputTypeName(typeName string) string {
for i := range args {
args[i] = inputTypeName(args[i])
}
return "typing.Union[" + strings.Join(args, ", ") + "]"
return strings.Join(args, " | ")
case strings.HasPrefix(typeName, "typing.Optional[") && strings.HasSuffix(typeName, "]"):
return "typing.Optional[" + inputTypeName(typeName[len("typing.Optional["):len(typeName)-1]) + "]"
return inputTypeName(typeName[len("typing.Optional["):len(typeName)-1]) + " | None"
case isPrimitiveType(typeName):
return typeName
default:
Expand Down
63 changes: 63 additions & 0 deletions codegen/pkg/builder/types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package builder

import "testing"

func TestGeneratedTypesUseModernUnionSyntax(t *testing.T) {
tests := []struct {
name string
got string
want string
}{
{
name: "optional property",
got: (&Property{
Name: "expires_at",
Type: "datetime.datetime",
Optional: true,
}).String(),
want: "expires_at: datetime.datetime | None = None\n",
},
{
name: "optional method parameter",
got: (&Property{
Name: "status",
Type: "str",
Optional: true,
}).MethodParameterString(true),
want: "status: str | None | NotGivenType = NOT_GIVEN",
},
{
name: "enum",
got: (&EnumDeclaration[string]{
Name: "Status",
Type: "string",
Values: []string{"pending", "paid"},
}).String(),
want: "Status = typing.Literal[\"paid\", \"pending\"] | str\n",
},
{
name: "one of",
got: (&OneOfDeclaration{
Name: "CheckoutResponse",
Options: []string{"CheckoutSuccess", "CheckoutAccepted"},
}).String(),
want: "CheckoutResponse = CheckoutSuccess | CheckoutAccepted\n",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.got != tt.want {
t.Fatalf("generated type = %q, want %q", tt.got, tt.want)
}
})
}
}

func TestInputTypeNameConvertsLegacyUnionSyntax(t *testing.T) {
got := inputTypeName("typing.Union[Checkout, typing.Optional[list[Address]]]")
want := "CheckoutInput | typing.Sequence[AddressInput] | None"
if got != want {
t.Fatalf("inputTypeName() = %q, want %q", got, want)
}
}
29 changes: 14 additions & 15 deletions codegen/templates/client.py.tmpl
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
# Code generated by `py-sdk-gen`. DO NOT EDIT.
import os
import httpx
import typing

from ._service import Resource, AsyncResource, runtime_headers
{{- range .Resources }}
from .{{ .Package }} import {{ .Name }}Resource, Async{{ .Name }}Resource
{{- end }}

BASE_URL = "https://api.sumup.com"
TimeoutType = typing.Union[
httpx.Timeout,
float,
tuple[
typing.Optional[float],
typing.Optional[float],
typing.Optional[float],
typing.Optional[float],
],
]
TimeoutType = (
httpx.Timeout
| float
| tuple[
float | None,
float | None,
float | None,
float | None,
]
)


class Sumup(Resource):
def __init__(
self,
api_key: typing.Optional[str] = None,
api_key: str | None = None,
base_url: str = BASE_URL,
timeout: typing.Optional[TimeoutType] = None,
timeout: TimeoutType | None = None,
) -> None:
self.api_key = api_key or os.getenv("SUMUP_API_KEY")
super().__init__(httpx.Client(
Expand All @@ -53,9 +52,9 @@ class Sumup(Resource):
class AsyncSumup(AsyncResource):
def __init__(
self,
api_key: typing.Optional[str] = None,
api_key: str | None = None,
base_url: str = BASE_URL,
timeout: typing.Optional[TimeoutType] = None,
timeout: TimeoutType | None = None,
) -> None:
self.api_key = api_key or os.getenv("SUMUP_API_KEY")
super().__init__(httpx.AsyncClient(
Expand Down
4 changes: 2 additions & 2 deletions codegen/templates/resource.py.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class {{.Service}}Resource(Resource):
{{- with .DeprecationNotice }}
@typing_extensions.deprecated("{{ . }}")
{{- end }}
def {{.FunctionName}}({{.ParamsString}}, headers: typing.Optional[HeaderTypes] = None){{ with .ResponseType }} -> {{ . }}{{ end }}:
def {{.FunctionName}}({{.ParamsString}}, headers: HeaderTypes | None = None){{ with .ResponseType }} -> {{ . }}{{ end }}:
{{- with .Description}}
'''
{{ indent 2 . }}
Expand Down Expand Up @@ -97,7 +97,7 @@ class Async{{.Service}}Resource(AsyncResource):
{{- with .DeprecationNotice }}
@typing_extensions.deprecated("{{ . }}")
{{- end }}
async def {{.FunctionName}}({{.ParamsString}}, headers: typing.Optional[HeaderTypes] = None){{ with .ResponseType }} -> {{ . }}{{ end }}:
async def {{.FunctionName}}({{.ParamsString}}, headers: HeaderTypes | None = None){{ with .ResponseType }} -> {{ . }}{{ end }}:
{{- with .Description}}
'''
{{ indent 2 . }}
Expand Down
2 changes: 1 addition & 1 deletion examples/card_reader_checkout.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
import asyncio
import os

from sumup import AsyncSumup

Expand Down
2 changes: 1 addition & 1 deletion examples/sync.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os

from sumup import Sumup, APIError
from sumup import APIError, Sumup

client = Sumup()

Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ check-fix:

# Generate code from OpenAPI specs
[group('misc')]
generate: && fmt check-fix
generate: && check-fix fmt
go -C codegen run . generate \
--out ../sumup/ \
../openapi.json
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "sumup"
description = "Official Python SDK for the SumUp API."
version = "0.3.1"
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.10,<3.15"
keywords = ["sdk", "sumup", "payments"]
authors = [{name = "SumUp", email = "support@sumup.com"}]
dependencies = [
Expand Down
4 changes: 2 additions & 2 deletions sumup/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from sumup._client import Sumup, AsyncSumup
from sumup._service import Resource, AsyncResource
from sumup._client import AsyncSumup, Sumup
from sumup._exceptions import APIError
from sumup._service import AsyncResource, Resource

__all__ = ["APIError", "AsyncResource", "AsyncSumup", "Resource", "Sumup"]
Loading
Loading