Data pipelines and transformation
Where the ecosystem is genuinely unmatched and the alternative would mean rebuilding tooling that already exists and is well understood.
Python's productivity is real and so is its principal risk: it permits structure rather than requiring it. Codebases that stay maintainable are the ones where that discipline was imposed deliberately and early.
Python has become the default language for data engineering, automation and increasingly for service development, largely because the ecosystem for data work is unmatched and because it is quick to write.
The trade-off is that the language enforces very little. Type correctness, module boundaries and interface contracts are all optional, and a codebase that grows without them becomes difficult to change safely — not because Python is unsuitable for large systems, but because the discipline that other languages impose has to be chosen here.
Our work in Python is mostly data platform engineering and service architecture, and a recurring part of it is introducing the structure — typing, packaging, testing, dependency management — that lets a productive codebase stay productive past its second year.
Where we see this used to good effect, and what makes each case a fit.
Where the ecosystem is genuinely unmatched and the alternative would mean rebuilding tooling that already exists and is well understood.
Training pipelines, feature engineering and the platform around models, where the library ecosystem is the deciding factor.
Operational automation and internal tools where development speed matters more than raw runtime performance.
Services where the team is already fluent and the throughput requirement is within what the runtime comfortably supports.
Reconciliation, analysis and reporting where iteration speed and library availability outweigh execution efficiency.
Connecting systems where the work is mostly transformation and the libraries for both ends already exist.
The architectural decision that matters most in Python is packaging and module boundaries, because nothing enforces them. A codebase where any module can import any other becomes a graph rather than a structure, and changes then have consequences nobody predicted.
The second is where Python belongs at all. It is an excellent choice for data work and a questionable one for CPU-bound, latency-critical services — and being explicit about that boundary is more useful than defending the language in general.
The practices that separate maintainable Python from the alternative are all about adding constraints the language omits: static type checking in the build, linting and formatting enforced automatically, and reproducible dependency resolution with a lock file.
Type checking is the one that pays back most and is most often adopted halfway. Partial typing gives a false impression of coverage, because the checker cannot reason about the untyped portions and reports silence rather than uncertainty.
Python's dynamic nature makes some vulnerability classes easier to introduce: unsafe deserialisation, dynamic evaluation of input, and template injection are all straightforward mistakes that no compiler will catch.
Dependency provenance is the second concern. The package index is open, name-similarity attacks are a known technique, and a build that installs unpinned packages from a public index without scanning trusts a supply chain nobody has reviewed.
Python's concurrency model is the constraint most teams meet. The global interpreter lock limits CPU-bound parallelism within a process, which is immaterial for I/O-bound work and decisive for CPU-bound work.
The practical answer is process-level scaling for services and distributed processing for data workloads, both of which work well — provided the design accounted for them rather than assuming threads would suffice.
Python is slower than compiled alternatives and that is usually irrelevant, because most workloads are bound by I/O or by the database rather than by interpretation. Optimising Python code before profiling is reliably wasted effort.
Where execution speed genuinely matters, the established answer is to push the hot path into a library that is not written in Python, which the data ecosystem already does extensively. Rewriting the whole service in another language is a much larger step than the problem usually warrants.
Python integrates with almost everything, which is a genuine strength and means integration decisions are architectural rather than technical: the contract, the failure behaviour and the schema owner.
In data engineering specifically, the recurring issue is schema. Python makes it easy to process data whose shape is assumed rather than declared, and a pipeline that infers schema will eventually infer a different one without telling anybody.
Operating Python services is straightforward with the usual foundations: structured logging, health endpoints and graceful shutdown. The Python-specific concern is environment reproducibility — a service that runs differently depending on how its dependencies resolved is difficult to diagnose.
For data pipelines, the operational requirement differs: alerting on absence and freshness rather than on process failure, because the failure that matters most is data that never arrived.
Governance in Python estates is mostly about consistency, because the language offers so many valid ways to structure the same thing. Agreeing packaging, tooling, typing standards and testing conventions once removes an enormous amount of per-project variation.
The second area is the boundary between analysis and production. Notebook-based exploratory work is valuable and is not production code, and the path from one to the other should be explicit rather than accidental.
Sectors where this comes up most often, and the constraint that puts it there.
OSS/BSS complexity, network-scale data volumes and availability expectations measured in minutes per year.
IndustryOperational technology meeting information technology, supply chain visibility, and ERP estates grown by accretion.
IndustryGrid and asset telemetry, regulatory reporting, and operational systems that cannot tolerate an outage window.
IndustryReal-time tracking, partner system integration, and routing systems where latency has a direct cost.
IndustryCore system modernisation, real-time payment flows, and architecture that can evidence regulatory controls.
IndustryInteroperability between clinical systems, patient data privacy, and legacy platforms that cannot be taken offline.
The engagements this work is usually delivered within.
Data architecture, platform modernisation, integration and analytics — the foundations that have to exist before AI is a sensible conversation.
Custom application, web, mobile and API development, and modernisation of systems that are constraining the business.
Target-state design, modernisation roadmaps, integration and cloud architecture, and the governance that keeps decisions consistent after the consultants leave.
Delivery pipelines, infrastructure as code, Kubernetes and the observability practices that make releases routine.
Test strategy, automation and performance testing, so quality is measured continuously rather than discovered late.
Migration strategy, landing-zone design, cost modelling and the architecture decisions that determine whether cloud spend buys capability.
Yes, with the structure the language does not impose: type checking in the build, enforced package boundaries and reproducible dependencies. Codebases that become unmaintainable are not failures of the language but of the decision to skip constraints that were optional.
For CPU-bound, latency-critical services, and where the team has no Python fluency and no reason to acquire it. Being explicit about that boundary is more useful than defending the language generally — most estates should use it for some things and not others.
Important enough to enforce in the build. They catch a class of error that otherwise reaches production, and they make refactoring viable in a codebase nobody fully remembers. Partial adoption is the trap: the checker cannot reason about untyped portions and reports silence rather than uncertainty.
Only if your work is CPU-bound within a process. For I/O-bound services and for data workloads that distribute across processes it is largely immaterial. The mistake is designing for threads and discovering the constraint later, rather than choosing the concurrency model deliberately.
Profile first — intuition about what is expensive in Python is unusually unreliable. Then look at data access, which usually dominates. Where execution genuinely is the constraint, push the hot path into an optimised library rather than rewriting the service in another language.
Locked, hashed and reproducible, with a documented update path. The package index is open and name-similarity attacks are a known technique, so a build installing unpinned packages from a public index is trusting a supply chain nobody has reviewed.
Alerting on absence and freshness rather than only on job failure. The failure that matters most in a pipeline is data that never arrived, and a job-status check cannot detect it — the job succeeded, there was simply nothing to process.
Not as they are, and the path from one to the other should be explicit rather than accidental. Exploratory work is genuinely valuable and has different properties from production code — reproducibility, testing, error handling — and conflating the two is how fragile pipelines appear.
By declaring and validating schema at boundaries rather than inferring it. Python makes it easy to process data whose shape is assumed, and a pipeline that infers schema will eventually infer a different one and continue without telling anybody.
Preferably not. Python offers many valid ways to structure the same thing, so agreeing packaging, tooling and testing conventions once removes a large amount of per-project variation and makes moving engineers between codebases considerably cheaper.
Yes — it is the most common language in our data engineering work, for the pipelines, quality testing and identity resolution that platform requires.
Discuss a Python codebase or data platform
A free first consultation covering what you are building, whether Python is the right choice for it, and what structure the codebase needs to stay maintainable past its second year.
No cost, no obligation. Pricing is transparent and includes GST.