Skip to content

Commit 4e4ad35

Browse files
feat: adopt shared plugin authoring APIs
1 parent 56086a2 commit 4e4ad35

6 files changed

Lines changed: 53 additions & 67 deletions

File tree

‎agent.codefly.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
publisher: codefly.dev
22
name: python
3-
version: 0.0.7
3+
version: 0.0.8

‎go.mod‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.25.0
55
toolchain go1.26.4
66

77
require (
8-
github.com/codefly-dev/core v0.2.11
8+
github.com/codefly-dev/core v0.2.16
99
gopkg.in/yaml.v3 v3.0.1
1010
)
1111

‎go.sum‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJ
3333
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
3434
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
3535
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
36+
github.com/codefly-dev/core v0.2.16 h1:94gqpcizi64wSevD1ETk/G1nRSAVawlwYuW7AGFj2ak=
37+
github.com/codefly-dev/core v0.2.16/go.mod h1:78gNY4qIlPnj08Fe8X5WwXWtqjTvU4MelJlOYTJq/5s=
3638
github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1hQ=
3739
github.com/codefly-dev/gortk v0.2.0/go.mod h1:dDWUMFgAP063OCGhTEpV7FFUj9+zTcxxO/nV80VKEwg=
3840
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=

‎pkg/builder/builder.go‎

Lines changed: 16 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Package builder implements the generic Python Builder gRPC service.
2-
// Its job on the generic layer is minimal: Load/Init plumbing, a no-op
3-
// Build (uv sync happens at Runtime.Init), a no-op Sync (no protos).
2+
// Its job on the generic layer is minimal: Load/Init plumbing and Configure.
3+
// Shared successful no-op phases come from services.DefaultBuilder.
44
// Specializations override Build (to produce container images) and Sync
55
// (to generate protos / adapters / migrations).
66
package builder
@@ -20,34 +20,25 @@ import (
2020
// Builder is the generic Python builder server. Embedded by specializations
2121
// so they inherit Load/Init and only add what their layer needs.
2222
type Builder struct {
23-
services.BuilderServer
23+
*services.DefaultBuilder
2424
*pythonservice.Service
2525
}
2626

2727
// New builds a generic Python Builder bound to the shared Service.
2828
func New(svc *pythonservice.Service) *Builder {
29-
return &Builder{Service: svc}
29+
return &Builder{
30+
DefaultBuilder: services.NewDefaultBuilder(svc.Builder),
31+
Service: svc,
32+
}
3033
}
3134

3235
// Load reads the identity and settings. Specializations override to wire
3336
// endpoints and compute layer-specific paths, but should call this first.
3437
func (s *Builder) Load(ctx context.Context, req *builderv0.LoadRequest) (*builderv0.LoadResponse, error) {
3538
defer s.Wool.Catch()
36-
ctx = s.Wool.Inject(ctx)
37-
38-
if err := s.Builder.Load(ctx, req.Identity, s.Service.Settings); err != nil {
39-
return s.Builder.LoadError(err)
40-
}
41-
39+
response, err := s.Builder.LoadService(ctx, req, services.BuilderLoad{Settings: s.Service.Settings})
4240
s.Service.SourceLocation = s.Location
43-
44-
if req.CreationMode != nil {
45-
s.Builder.CreationMode = req.CreationMode
46-
return s.Builder.LoadResponse()
47-
}
48-
49-
s.Endpoints, _ = s.Base.Service.LoadEndpoints(ctx)
50-
return s.Builder.LoadResponse()
41+
return response, err
5142
}
5243

5344
// Init records dependency endpoints.
@@ -59,46 +50,21 @@ func (s *Builder) Init(ctx context.Context, req *builderv0.InitRequest) (*builde
5950
return s.Builder.InitResponse()
6051
}
6152

62-
// Update is a no-op on the generic layer.
63-
func (s *Builder) Update(ctx context.Context, _ *builderv0.UpdateRequest) (*builderv0.UpdateResponse, error) {
64-
defer s.Wool.Catch()
65-
ctx = s.Wool.Inject(ctx)
66-
return &builderv0.UpdateResponse{}, nil
67-
}
68-
69-
// Sync is a no-op on the generic layer — Python has no protos to generate.
70-
// Specializations (python-fastapi, python-grpc) override to regenerate
71-
// their protocol artifacts.
72-
func (s *Builder) Sync(ctx context.Context, _ *builderv0.SyncRequest) (*builderv0.SyncResponse, error) {
73-
defer s.Wool.Catch()
74-
ctx = s.Wool.Inject(ctx)
75-
return s.Builder.SyncResponse()
76-
}
77-
78-
// Build is a no-op on the generic layer — uv sync at Runtime.Init handles
79-
// Python dep resolution; there's no artifact to produce. Specializations
80-
// override to build container images.
81-
func (s *Builder) Build(ctx context.Context, _ *builderv0.BuildRequest) (*builderv0.BuildResponse, error) {
82-
defer s.Wool.Catch()
83-
ctx = s.Wool.Inject(ctx)
84-
return s.Builder.BuildResponse()
85-
}
86-
87-
// Deploy is a no-op on the generic layer. Specializations override to
88-
// render k8s manifests or push images.
53+
// Deploy fails explicitly on the generic layer. Returning a success response
54+
// without producing a workload made the CLI report a deployment that did not
55+
// exist. Deployable specializations override this method.
8956
func (s *Builder) Deploy(ctx context.Context, _ *builderv0.DeploymentRequest) (*builderv0.DeploymentResponse, error) {
9057
defer s.Wool.Catch()
9158
ctx = s.Wool.Inject(ctx)
92-
return s.Builder.DeployResponse()
59+
return s.Builder.DeployError(fmt.Errorf("generic Python services do not define a deployable workload; use a deployable specialization such as python-fastapi"))
9360
}
9461

95-
// Create is a no-op on the generic layer. A generic "python service" has
96-
// no inherent template — it's an abstract starting point. Specializations
97-
// ship their own templates/factory/ tree and override Create to apply it.
62+
// Create likewise fails explicitly because the generic layer has no project
63+
// template. Specializations provide their own factory tree.
9864
func (s *Builder) Create(ctx context.Context, _ *builderv0.CreateRequest) (*builderv0.CreateResponse, error) {
9965
defer s.Wool.Catch()
10066
ctx = s.Wool.Inject(ctx)
101-
return s.Builder.CreateResponse(ctx, s.Service.Settings)
67+
return s.Builder.CreateError(fmt.Errorf("generic Python services do not define a project template; use a concrete specialization"))
10268
}
10369

10470
// Configure applies structured config changes to the service's test formula and

‎pkg/builder/builder_test.go‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package builder_test
22

33
import (
4+
"context"
5+
"strings"
46
"testing"
57

8+
builderv0 "github.com/codefly-dev/core/generated/go/codefly/services/builder/v0"
69
"github.com/codefly-dev/core/resources"
710

811
pythonbuilder "github.com/codefly-dev/service-python/pkg/builder"
@@ -24,3 +27,24 @@ func TestBuilderEmbedsService(t *testing.T) {
2427
_ = b.Base
2528
_ = b.Builder
2629
}
30+
31+
func TestGenericBuilderRejectsUnsupportedCreateAndDeploy(t *testing.T) {
32+
svc := pythonservice.New(&resources.Agent{Kind: "codefly:service", Name: "python"})
33+
b := pythonbuilder.New(svc)
34+
35+
created, err := b.Create(context.Background(), &builderv0.CreateRequest{})
36+
if err != nil {
37+
t.Fatal(err)
38+
}
39+
if created.GetState().GetState() != builderv0.CreateStatus_ERROR || !strings.Contains(created.GetState().GetMessage(), "concrete specialization") {
40+
t.Fatalf("unexpected create response: %+v", created)
41+
}
42+
43+
deployed, err := b.Deploy(context.Background(), &builderv0.DeploymentRequest{})
44+
if err != nil {
45+
t.Fatal(err)
46+
}
47+
if deployed.GetState().GetState() != builderv0.DeploymentStatus_ERROR || !strings.Contains(deployed.GetState().GetMessage(), "python-fastapi") {
48+
t.Fatalf("unexpected deploy response: %+v", deployed)
49+
}
50+
}

‎pkg/runtime/runtime.go‎

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import (
3333
// calling RegisterReplCommands once from their own Load — same pattern
3434
// go-grpc uses for its explicit registerCommands call.
3535
type Runtime struct {
36-
services.RuntimeServer
36+
*services.DefaultRuntime
3737
*pythonservice.Service
3838

3939
// Persistent Python REPL — started lazily on first `exec` command,
@@ -45,7 +45,10 @@ type Runtime struct {
4545

4646
// New builds a generic Python Runtime bound to the shared Service.
4747
func New(svc *pythonservice.Service) *Runtime {
48-
return &Runtime{Service: svc}
48+
return &Runtime{
49+
DefaultRuntime: services.NewDefaultRuntime(svc.Runtime),
50+
Service: svc,
51+
}
4952
}
5053

5154
// Load reads the agent identity, stores environment, and resolves the
@@ -54,23 +57,18 @@ func New(svc *pythonservice.Service) *Runtime {
5457
func (s *Runtime) Load(ctx context.Context, req *runtimev0.LoadRequest) (*runtimev0.LoadResponse, error) {
5558
defer s.Wool.Catch()
5659

57-
if err := s.Base.Load(ctx, req.Identity, s.Service.Settings); err != nil {
58-
return s.Runtime.LoadErrorf(err, "loading base")
59-
}
60-
61-
s.Runtime.SetEnvironment(req.Environment)
60+
response, err := s.Runtime.LoadService(ctx, req, services.RuntimeLoad{Settings: s.Service.Settings})
6261

6362
// Python convention: source is the service directory itself.
6463
s.Service.SourceLocation = s.Location
6564
if wd := os.Getenv("CODEFLY_AGENT_WORKDIR"); wd != "" {
6665
s.Service.SourceLocation = wd
6766
}
6867

69-
resp, err := s.Runtime.LoadResponse()
70-
if resp != nil && resp.Status != nil {
71-
resp.Status.Message = pythonhelpers.AppendRuntimeEvidence(resp.Status.Message, pythonhelpers.RuntimeEvidence(s.Service.SourceLocation))
68+
if response != nil && response.Status != nil {
69+
response.Status.Message = pythonhelpers.AppendRuntimeEvidence(response.Status.Message, pythonhelpers.RuntimeEvidence(s.Service.SourceLocation))
7270
}
73-
return resp, err
71+
return response, err
7472
}
7573

7674
// Init runs `uv sync` to materialize the venv. Non-fatal on failure.
@@ -229,10 +227,6 @@ func (s *Runtime) Build(_ context.Context, _ *runtimev0.BuildRequest) (*runtimev
229227
return &runtimev0.BuildResponse{}, nil
230228
}
231229

232-
func (s *Runtime) Information(ctx context.Context, req *runtimev0.InformationRequest) (*runtimev0.InformationResponse, error) {
233-
return s.Runtime.InformationResponse(ctx, req)
234-
}
235-
236230
// resolveTestFormula picks the test formula to run: a per-call
237231
// TestRequest.formula wins, else the service's language-agnostic config formula
238232
// (resources.Service.Test), else the formula DERIVED from the project, else none

0 commit comments

Comments
 (0)