Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Events
Videos
Audiobooks
Packt Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Hands-On Software Engineering with Python
Hands-On Software Engineering with Python

Hands-On Software Engineering with Python: Move beyond basic programming to design, maintain, and deploy extensible Python systems , Second Edition

Arrow left icon
Profile Icon Brian Allbee
Arrow right icon
€37.99
Paperback Dec 2025 628 pages 2nd Edition
eBook
€26.99 €29.99
Paperback
€37.99
Arrow left icon
Profile Icon Brian Allbee
Arrow right icon
€37.99
Paperback Dec 2025 628 pages 2nd Edition
eBook
€26.99 €29.99
Paperback
€37.99
eBook
€26.99 €29.99
Paperback
€37.99

What do you get with Print?

Product feature icon Instant access to your digital copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Redeem a companion digital copy on all Print orders
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Table of content icon View table of contents Preview book icon Preview Book

Hands-On Software Engineering with Python

Introduction

Pursuing a career in software engineering implies, at a minimum, a certain tolerance for change, if not an active pursuit or embrace of change. Processes and best practices evolve over time, as do the tools and even the languages themselves. While there are not a lot of truly new languages that have been released in the seven years since the first edition of this book was published, several languages have become more popular, including Go, Kotlin, Rust, and TypeScript. Ideas that appear in one language may surface in another, when the authors or maintainers of the language decide that the ideas are worth incorporating. Possible examples of that sort of cross-pollination, with capabilities being implemented in Python that may have originated in other languages, include the property decorator (.NET Framework had recognizably similar capabilities a year earlier) and the ability to annotate or type-hint functions and callables (a key capability of TypeScript, version 1.0 of which was released a year prior to Python’s support for the idea).

Even the titles have the potential for change. The previous edition of this book started with a breakdown of the various levels, grades, or ranks that organizations often use to indicate degrees of experience, expertise, and wisdom expected of their development personnel.

Those categories have not changed significantly in the intervening years:

  • At a junior or associate level, the aspiring software engineer is typically someone who does not have much experience. They probably know the basics of writing code, but are not expected to know much more than that.
  • The level between junior and senior is typically where the first real exposure to and experience with software engineering starts to happen: Understanding how different pieces of code interact and come together as a system, and the principles involved in the design of systems rather than just writing code, are a major portion of the growth or knowledge expected.
  • A senior-level practitioner has enough experience, even if it is focused on a very specific set of products, projects, or systems, to firmly grasp all of the technical skills involved in typical software development efforts. There is also, typically, a solid handle on the non-technical or semi-technical skills involved as well. Key among those are policies, procedures, strategies, and tactics that encourage or enforce business values like stability of a final product and predictability of development efforts. Seniors may not be experts in those areas, but are expected to recognize and call out risks, and provide options and suggestions for mitigating those risks before they become actual issues.

Typical title breakdowns for these levels include junior/associate developer/software developer/software engineer; developer and software engineer, sometimes with organization-specific suffixes; and senior developer/software developer/software engineer. In the past several years, a new category has become common enough that it bears mention and discussion: staff engineer. Staff engineers are senior-level technical leaders who can provide guidance on complex problems and systems, architecture, system and design strategies, and perhaps more in the context of the organizations they work for.

Staff engineering references

Staff engineer as a position or job title is new enough that it may still be in flux. Many of the basic concepts that drove the idea in the first place are described in detail in Staff Engineer: Leadership Beyond the Management Track by Will Larson, and The Staff Engineer’s Path: A Guide For Individual Contributors Navigating Growth and Change, by Tanya Reilly.

The dividing line between programming and software engineering falls somewhere within the differences between the mid- and senior-level titles, as far as technical capabilities and expertise are concerned. At a junior level, and to a lesser extent at the mid-level titles, efforts are often centered around nothing more than writing code to meet whatever requirements apply, and conforming to whatever standards are in play. Software engineering, at a senior developer level, has a bigger picture view of the same end results.

The bigger picture involves awareness of, and attention paid to, the following things:

  • Standards, both technical/developmental and otherwise, including best practices
  • The goals that code is written to accomplish, including the business values that are attached to them
  • The shape and scope of the entire system that the code is a part of

Free Benefits with Your Book

Your purchase includes a free PDF copy of this book along with other exclusive benefits. Check the Free Benefits with Your Book section in the Preface to unlock them instantly and maximize your learning experience.

The bigger picture

So, what does this bigger picture look like? There are three easily identifiable areas of focus, with a fourth (call it user interaction) that either weaves through the other three or is broken down into its own groups.

Software engineering must pay heed to standards, especially non-technical (business) ones, as well as to best practices. These may or may not be followed but, since they are standards or best practices for a reason, not following them is something that should always be a conscious (and defensible) decision. It’s not unusual for business process standards and practices to span multiple software components, which can make them difficult to track if a certain degree of discipline and planning isn’t factored into the development process to make them more visible. On the purely development-related side, standards and best practices can drastically impact the creation and upkeep of code, its ongoing usefulness, and even just the ability to find a given chunk of code, when necessary.

It’s rare for code to be written simply for the sake of writing code. There’s almost always some other value associated with it, especially if there’s business value or actual revenue associated with a product that the code is a part of. In those cases, understandably, the people who are paying for the developmental effort will be very interested in ensuring that everything works as expected (code quality) and can be deployed when expected (process predictability).

The remaining policy and procedure-related concerns are generally managed by setting up and following various standards, processes, and best practices during the startup of a project (or perhaps a development team). Those items — things such as setting up source control, having standard coding conventions, and planning for repeatable, automated testing — will be examined in some detail in later chapters. Ideally, once these kinds of development processes are in place, the ongoing activities that keep them running and reliable will just become habits, a part of the day-to-day process, almost fading into the background.

Finally, with more of a focus on the code side, software engineering must, by necessity, pay heed to entire systems, keeping a universal view of the system in mind. Software is composed of a lot of elements that might be classified as atomic: they are indivisible units in and of themselves, under normal circumstances. Just like their real-world counterparts, when they start to interact, things get interesting, and hopefully useful. Unfortunately, that’s also when unexpected (or even dangerous) behaviors — bugs — usually start to appear.

This awareness is, perhaps, one of the more difficult items to cultivate. It relies on knowledge that may not be obvious, documented, or readily available. In large or complex systems, it may not even be obvious where to start looking, or what kinds of questions to ask to try to find the information needed to acquire that knowledge.

Asking questions

There can be as many distinct questions that can be asked about any given chunk of code as there are chunks of code to ask about — even very simple code, living in a complex system, can raise questions in response to questions, and more questions in response to those questions.

If there isn’t an obvious starting point, starting with the following really basic questions is a good first step:

  1. Who will be using the functionality?
  2. What will they be doing with it?
  3. When, and where, will they have access to it?
  4. What problem is it trying to solve? For example, why do they need it?
  5. How does it have to work? If detail is lacking, breaking this one down into two separate questions is useful:
    • What should happen if it executes successfully?
    • What should happen if the execution fails?

Teasing out more information about the whole system usually starts with something as basic as the following questions:

  • What other parts of the system does this code interact with?
  • How does it interact with them?

Having identified all of the moving parts, thinking about “What happens if…” scenarios is a good way to identify potential points where things will break, risks, and dangerous interactions. You can ask questions like these to start discovering these points in the code:

  • What happens if this argument, which expects a number, is handed a string?
  • What happens if that property isn’t the object that’s expected?
  • What happens if some other object tries to change this object while it’s already being changed?

Whenever one question has been answered, simply ask, What else? This can be useful for verifying whether the current answer is reasonably complete.

Picking a Python version for a project

When the work on this book started, Python 3.11 was selected as the language version that code would be written in. There were several reasons for that selection, but the primary one was that it had been available for long enough to be in a security maintenance cycle, and with no expectation of significant changes other than for security issues. That was early in 2024, and support for the version is, as the book is being finished, going to continue for another two years, ending in October of 2027. Hand in hand with that was the fact that the bulk of the work I was doing was writing and maintaining AWS Lambda Functions written in Python, and although the 3.12 version was available for those purposes, it was new enough that I didn’t have a lot of hands-on experience with it in that context.

The Python Software Foundation, the maintainers of the language, have historically been very good about publishing comprehensive documentation of the changes to the language as each new minor version is released. Their documentation for any given version can be found online at https://www.python.org/doc/versions/, and is updated as each new version is released, including the patch versions.

Python versioning follows Semantic Versioning (SEMVER)

Semantic versioning defines a version naming standard that identifies major, minor, and patch versions. In formal SEMVER, major version changes, like the change from Python 2.x to 3.x, indicate significant, incompatible changes with the previous major version. Minor version changes, for example, from 3.11 to 3.12, indicate the addition of functionality in a backward-compatible manner, and patch version changes — 3.11.12 to 3.11.13 — indicate bug fixes that are implemented in a backward-compatible manner. Python releases may not strictly follow SEMVER standards, though I cannot point to an example where that has not been the case. Typically, as features and functionality in Python are slated for removal, they are flagged as deprecated — no longer recommended for use, and planned for removal — and will raise warnings as code that uses them is executed.

Python’s release schedule and support processes are well documented online (see https://devguide.python.org/versions/), with a cadence of approximately five years between the initial release of a minor version and its official end of life. In more recent versions, the first two years or so of a given version’s life include a bugfix support phase, and the remaining three years are limited to security support. Online providers will frequently follow the life cycle of any given Python version with respect to their support of that version, though they may allow continued use of a version that has officially reached its end of life. By way of example, Amazon Web Services’ (AWS’) Python version support, as of the end of 2025, included Python 3.9, with an official end of life in October of 2025, was a supported version, with its deprecation planned in December 2025, and limitations on the ability to create or update Lambda Functions using that version starting early in 2026.

So, taking all of these factors into consideration, the determination of what Python version to use for any given project is a decision that needs to be made based on a number of factors:

  • As a general rule, the most recent version available may be preferable, simply because it will provide the most current capabilities that the language provides, and will have the longest lifespan.
  • Restrictions or constraints by a provider may limit those options: Using AWS as an example again, they appear to start supporting a given version of Python about a month after it is released, but that may not always be the case. Other providers may have different limitations, or it may be that the OS where development work is being done imposes its own limitation (not uncommon for Linux systems that use Python for key applications or tools).
  • Even if a just-released new version is supported, there is some risk assessment that should be undertaken: The bugfix phase of the version will last for two years, and there is some risk that bug fixes implemented during that period will be problematic. The same holds true for versions that are in the security phase of their lives, though generally those have fewer issues on a release-by-release basis, likely because most of the bugs have already been dealt with by then.
  • Checking the Changelog for a given version (https://docs.python.org/release/3.11.13/whatsnew/changelog.html, for example, for 3.11), or the release notes (https://docs.python.org/3/whatsnew/3.11.html for 3.11 again) may also be useful if there are specific features that are needed or desired in the project, that may not be available or implemented as expected.

As of the end of 2025, assuming that the preference is to avoid versions in their bugfix phase, the most current version that fits that criteria is 3.12. Version 3.13 would be viable with the same considerations driving version selection by the end of 2026, and 3.14, expected to be released in October 2025, would be viable by the end of 2027.

Getting and installing Python

Installation of Python varies a bit across operating systems. For macOS and Windows systems, there are installers available for download on the Python website (https://www.python.org/downloads/) that generally take care of everything needed. Those installers include every version of the language going back as far as 2.0.1.

Installation of multiple versions of Python is possible, with caveats

Barring some special considerations for certain Linux distributions, installation of multiple different versions of Python is viable on a single machine. Each installation will typically provide python, python3, python3.xx, and sometimes python3.xx.yy command-line entries that can be used to run it. In cases where there are multiple Python 3.x installations, say 3.11 and 3.12, the most recently installed version will be executed by the python and python3 commands. If a specific version is needed, invoking it with the full python3.xx command will be necessary (e.g., python3.11 or python3.12).

For Linux systems, installation can be more problematic. Many Linux systems have Python installed by default, because some of their programs make use of it. In several cases, those distributions also limit the available versions of Python that can be installed, in order to prevent a user from accidentally breaking system components or programs. Checking the default software installation tools in a Linux distribution is always the best first step: If the specific version of Python that is needed/desired is available, installing it using the standard tools for the distribution is the safest process. The managers of the package repository for the distribution will have made reasonable efforts to prevent installations that can break a system from being available, and anything that is available as an option would be expected to be safe.

In cases where there are no alternative versions available, there are still options worth considering before going down the path of installing from the Python website, or building from source: pyenv and Homebrew.

pyenv (https://github.com/pyenv/pyenv) is a command-line tool that allows a user to install, manage, and switch between different versions of Python at will. While switching between versions has many of the same risks as installing other versions, pyenv's ability to download and install minimal, usable Python versions without interfering with the system-level installations makes it a very good candidate for cases where project- and system-level Python version requirements conflict with each other. pyenv is also recognized by at least one Python project management tool, pipenv, and integrated well enough that the creation of a new project environment with a new Python version is handled by the project manager almost seamlessly.

Homebrew (https://brew.sh/) is a more general-purpose software management tool, capable of installing various Python versions as well as many other software packages. It is not available for Windows, at least as of late 2025, but is a viable option for macOS and Linux systems where multiple Python versions are needed.

If neither of those options is workable, falling back to downloading from the Python website is always possible too. Because of the widely varied package structures across the Linux ecosystem, those downloads are the source code and would require building the local installation from scratch. If this path must be taken, it is important to find a good, step-by-step breakdown of the processes and prerequisites involved. Usually a search along the lines of {your Linux distro name} build python 3.13 from source will yield several options.

What’s changed in Python since the last edition

The code in the first edition of this book was written in Python 3.6, with an eye toward compatibility with 3.7. The code in this edition was written in Python 3.11, and by the time it sees print, Python 3.14 will have been released. Over each of those versions, there have been a number of changes, many of which will not come into play in the balance of this book, but are, nonetheless, worth knowing about.

Python 3.6

Python 3.6 introduced f-strings: strings that allow variable names and operations to be defined within them to be interpreted at run time. F-strings are used frequently in the code in this edition, particularly in log messages where error type names and values are expected to change. For example, here is an error-handling block that catches errors of any Exception type (the error), with any message, and logs the error type and message, along with the current variables (vars()) when the error is caught:

except Exception as error:
    logger.exception(
        f'{error.__class__.__name__}: {error} '
        'occurred in function_name'
    )
    logger.error('inputs: {vars()}')

Other changes included:

  • Allowing underscores to be used in numeric values as readability aids (for example, 1_000_000 instead of 1000000)
  • Allowing type annotations to be applied to variables (for example, my_var: int = 12 instead of my_var = 12)
  • Support for asynchronous use of generators (https://wiki.python.org/moin/Generators) and comprehensions (https://docs.python.org/3.11/tutorial/datastructures.html#list-comprehensions)

Python 3.7

Perhaps the most significant addition to the language in 3.7 was the addition of the dataclasses module (https://docs.python.org/3.7/library/dataclasses.html), providing a standard mechanism for defining classes whose primary intention is the storage of data, and tools for working with them. A simple dataclass, representing a person, might look like this:

from dataclasses import dataclass, asdict
@dataclass
class Person:
    given_name: str
    family_name: str
>>> ridcully = Person('Mustrum', 'Ridcully')
>>> print(ridcully)
Person(given_name='Mustrum', family_name='Ridcully')
>>> print(asdict(ridcully))
{'given_name': 'Mustrum', 'family_name': 'Ridcully'}

Other new features and improvements for this release are listed at https://docs.python.org/3/whatsnew/3.7.html.

Python 3.8

The walrus operator (:=), more formally known as assignment expressions, became available, allowing the assignment of values to variables as part of a larger, frequently conditional expression.

For example, taken from the What’s New In Python 3.8 page:

if (nlen(a) := ) > 10:
    print(
        f"List is too long ({n} elements, expected <= 10)"
    )

…allows the call to len(a) to be used once, assigning the result to n as part of the expression, rather than requiring it to be called again in the print statement.

Other new features and improvements are listed for this release at https://docs.python.org/3/whatsnew/3.8.html.

Python 3.9

New operators for dictionary types were introduced that allow merging (|) and updating (|=) dictionaries inline.

Other new features and improvements are listed for this release at https://docs.python.org/3/whatsnew/3.9.html.

Python 3.10

This release introduced structural pattern matching as an alternative to a series of if…elif… structures. For example:

match subject:
    case <pattern_1>:        #  if subject == <pattern_1>
        <action_1>           #    <action_1>
    case <pattern_2>:        #  if subject == <pattern_2>
        <action_2>           #    <action_2>
    case <pattern_3>:        #  if subject == <pattern_3>
        <action_3>           #    <action_3>
    case _:                  #  else:
        <action_wildcard>    #      <fallback

Other new features and improvements are listed for this release at https://docs.python.org/3/whatsnew/3.10.html.

Python 3.11

The major change in this release was an improvement to execution speed, ranging from 10 to 60 percent faster. A less obvious, but still significant, change was the deprecation of 22 modules, mostly concerned with older data formats no longer in common use. Of those, nine had one or more replacements already defined.

Other new features and improvements are listed for this release at https://docs.python.org/3/whatsnew/3.11.html.

Annotation changes were made in most of these releases

Annotation of function and method parameters, and of their return values, is discussed in some detail in Chapter 7, along with comparisons of what those changes allowed.

Changes in Python 3.12 onwards

Much of the focus in the more recent releases has been on reducing the requirement for the Global Interpreter Lock (GIL) in Python’s interpreter. The GIL is a mechanism that allows only one native processor thread to execute Python bytecode at any given time. While that lends a considerable amount of stability to running Python code, by preventing several types of memory-related issues, it also prevents truly parallel execution of Python code. In 3.12, the GIL was attached to individual interpreters, allowing sub-processes that run in their own sub-interpreters to have their own GIL, allowing for more true parallelism. The 3.13 release provided an experimental free-threaded build mode, allowing the GIL to be disabled entirely for even more truly parallel processing capabilities.

Another change to keep an eye on is the experimental Just-in-Time (JIT) compiler, which appears to be (potentially) laying the groundwork for translating Python’s bytecode to machine code.

These changes are the ones that I, personally, have found the most significant (or at least the most interesting) and do not represent anywhere near all of the changes that were made for each of these releases. These changes are, obviously, strictly limited to those that have happened to the language itself. There are other changes that have occurred in the time between the first edition and this edition that have little to do with the language, but with how the code gets written. Of those, the one that has made the most waves is undoubtedly the advent of AI for code generation.

The impact of AI on software engineering

As Chapters 13 and 14 were in progress, there were significant advances in various Large Language Model (LLM) tools toward writing and maintaining code. An LLM is a type of Artificial Intelligence (AI), trained on massive datasets to learn patterns and rules of language. Some of the more well-known LLMs as of 2025 include OpenAI’s GPT, Anthropic’s Claude, and xAI’s Grok. While the initial focus of LLMs was more toward natural human languages like English, it was just a matter of time before the idea of similar training for programming languages was considered and added into the mix. Early code generation using LLMs showed some promise, but was typically limited to generating small, independent chunks of code, with little to no reliability for integrating those small code blocks into larger-scoped projects. Over and above that, the code generated was not always reliably representative of what the intent was, as expressed to the LLM through a supplied prompt.

By the beginning of the second quarter of 2025, though, things had improved considerably. By way of example, when CodeGPT (https://chatgpt.com/g/g-cksUvVWar-code-gpt-python-java-c-html-javascript-more) was given this prompt:

I am writing a Python package as an example of AI-generated code. I need a project starting-point that I can download that follows a standard src-directory project structure, as well as having a tests directory with subdirectories for unit tests (unit), integration tests (integration) and system tests (system). Under the src directory, there need to be directories representing a package namespace “hms.core”, and there should be corresponding unit-test directories, each prefixed with “test_” for each level of the main namespace under the src directory. The project will use Pipenv to manage package dependencies, and should include, as development package requirements, the pytest, flake8, and coverage packages. Those packages’ versions should be pinned to versions less than the next major version; for example, the current version of pytest is 8.3.5, so the pytest installation in the Pipfile should indicate a version less than 9. The package project will eventually be published as a standard Python package, so there should be a pyproject.toml file that captures all the standard information and needs to accomplish that, and package installations for the “build” and “publish” categories of the Pipfile should include the “build” and “twine” packages, respectively. The project will eventually use some cloud- or SCM-resident CI/CD process, but for now, capture the necessary steps and processes in a standard Makefile that will eventually be used to create the final build-and-publish process. That Makefile should include running unit tests and all the other test-suites noted earlier.

Please generate this project structure, and provide it as a downloadable file, like a ZIP archive or a TAR file.

…the resulting project structure was reasonably complete, and was created in less than a minute, with perhaps five to ten minutes needed to write the entire prompt. Iterating over that initial project structure was possible, starting with the addition of functionality using this prompt:

Add a data_object.py module to the hms/core directory. This module will contain a class called BaseDataObject, which will use Pydantic Fields to define various properties. Those properties include “oid” an object ID, which will be a UUID value, providing the unique record identifier for objects in a relational database table later on. It will also include “created” and “modified”, both UTC dates, capturing the created date/time and last modified date/time of the object’s record. It will also include boolean fields called “is_active” and “is_deleted” which keep track of whether the record for a data object is active and deleted, respectively. These fields are all required. The oid field should default to a new UUID value, which can use the built-in uuid4 function. The date/time fields should default to the current UTC date/time. The is_active and is_deleted fields should default to False. All fields should have a description and two or more examples. The module should have an overall description.

BaseDataObject will also provide methods to perform CRUD operations: A get method will be used to retrieve one or more objects, and accept zero-to-many oid values (strings or UUIDs), include pagination control parameters that will be used to create a SQL “LIMIT” clause, and “criteria” that will be used to provide other selection criteria, based on equality, inequality, less-than/greater-than, and so on.

Update the project code and provide a new download with those changes.

…generated a reasonable starting point for the BaseDataObject and its get method, and continuing with:

The get method should be a class method, and should assume the use of parameterized queries with an execute method, following the Python database access API standards. Add a delete class method that accepts one-to-many oid values, that simulates the deletion of one to many records, and an instance method to save an instance’s data as well.

…also yielded reasonable starting-point code for those additions.

Different AIs will generate different results, and have different ways of presenting those results. For example, the same initial prompt, given to Anthropic’s Claude (https://claude.ai), yielded a Python script that generated the project structure, and the subsequent prompt that GPT Code used to add the get method Claude used to also generate the create, update, and delete methods.

Examples of LLM-generated code are in the chapter repository

The results from each of the LLM code generation efforts noted above are in the repository for this chapter: The Claude Code example is in the claude-example directory, and the CodeGPT example in code-gpt-example (https://github.com/PacktPublishing/Hands-On-Software-Engineering-with-Python-Second-Edition/tree/main/CH01-code/claude-example).

This sort of iterative, AI-assisted code generation has come to be known as vibe coding (https://en.wikipedia.org/wiki/Vibe_coding), and was a highly contentious topic in April of 2025, judging by the amount and types of discussions around it on LinkedIn and elsewhere on the Internet. The arguments for it include increased speed of output, which is certainly the case, and that the generated code is good enough, which is a very subjective judgment. The arguments against it usually centered around inconsistent and unpredictable code quality, the sheer volume of code that would have to be reviewed by a human engineer, and the lack of decision-making context that a human engineer just knows to keep in mind — things like paying heed to security concerns, which CodeGPT had to be told about (…should assume the use of parameterized queries with an execute method, following the Python database access API standards… in the second prompt above). Another concern, though its impact will vary from LLM to LLM, is the eventual loss of context by the LLM, leading to hallucinations: the generation of incorrect, misleading, or even nonsensical responses and information, typically with no warning or indication that it is happening. This is, ultimately, a function of how much context data a given LLM can keep track of, and that is bound to improve over time, but is a limiting factor whose scope may not be known.

The availability of AI agents — programs that leverage a backing AI system, but are able to keep track of the relevant context they are working in, make decisions, and take actions on their own to achieve specific goals — is another factor worth knowing about. As of this writing, I can only speak to one from personal experience: Claude Code. Claude Code appears able to keep track of a lot of context while it is working; I have used it to generate reasonably complete and functional code for creating and managing a REST API using AWS API Gateway that calls SageMaker endpoints, along with the Infrastructure as Code (IaC) to manage that API and the SageMaker endpoints it calls. Getting to that point required several days’ worth of prompt-writing and iteration over bugs that surfaced, and those efforts cost maybe $35 in Claude token purchases, but the experience was reasonable, most of the time, and the code generated was functional, if brute-forced and occasionally ugly.

Points to consider when using AI code assistants

Stick to one. Don’t change to another too frequently, even if there’s something much newer and better. Each LLM tends to have its own code style, and shifting between those is likely to be problematic over time.

Iterate in small chunks, and commit changes frequently. There are numerous stories being recounted online along the lines of [insert AI name here] destroyed my project, but the common thread among those that I’ve seen is a lack of tool and code discipline as changes are being made.

Decide on a trust boundary. Depending on how pessimistic you are, either don’t trust the AI-generated code at all, or trust it but verify it. In either case, be prepared to review a lot of auto-generated code, and tell the AI how to fix things that you do not like or agree with.

Be as specific as you can. Tell the AI what you want to accomplish, how it needs to be accomplished, what constraints are in play, etc., etc.

Set limitations on the AI. If there is code that should not be changed, tell the AI that it is not allowed to change that code, and stick to that. Similarly, apply standards as you see fit, even if they are just documentation standards.

TDD with an AI assistant

It seems likely to me that Test-Driven Development (TDD), where the automated tests are written before writing the code that will need to pass those tests, could be usefully combined with AI-assisted code generation. In teams where TDD is already in play, when combined with an agent like Claude Code (https://docs.anthropic.com/en/docs/agents-and-tools/claude-code) that can respond to events like test failures, and take corrective actions autonomously, there is real potential for fast, powerful, and verifiably useful code generation. Caution would need to be taken to make sure that the agent is not modifying tests to make them pass when modifying code to pass the tests is the approved remedy for those failures.

Is AI here to stay?

If I’m being brutally honest about this, my answer would have to be some variant of I just don’t know. The ability to generate large blocks of production-ready code quickly, effectively, and consistently would have to be among the highest-priority goals in that space. From what I’ve seen personally, the systems just aren’t there yet, not without a lot of review and input from an experienced, competent software engineer. Of those three points, the consistency feels to me like it’s the most challenging problem still to be solved. I’m not surprised by that, though: Given my admittedly limited understanding of how the LLMs that are behind these efforts work, and a certain amount of empirical observation, that the responses for any given prompt are… random but directed feels like as good a description as anything else. For example, I started two separate sessions with CodeGPT using the same prompt:

I would like you to generate some starting-point code, in Python, that will be used to keep track of a task-list for a locally-hosted web-application. The target audience is parents of children who have difficulties keeping track of their day-to-day obligations. The system should be able to keep track of those obligations (“chores”) and collections of “rewards” that they receive once their cores/obligations are complete.

Let’s start with a very high-level design for the backing logic for the application, concerned with representing the users, obligations, and rewards in the system, without being concerned yet about how those items’ data will be stored.

Despite the fact that the prompts were identical, the answers it came up with across those two sessions were significantly different, though there are at least some common elements between them. The class diagrams that were generated by that prompt in each session are shown in Figure 1.1, below.

Figure 1.1: The class diagrams from the same prompt in two different CodeGPT sessions

Both of these are viable starting points for the design of the application mentioned in the prompt. Both have significantly different approaches implied just from the class structures that the LLM came up with. At this level, with contexts established for each, progressing deeper into actual functionality would not be difficult, so long as the context doesn’t get lost by the LLM somewhere along the way. As soon as that starts happening, though, there will be an even greater need for the kind of software engineering expertise that the balance of this book is about. Without that, the random nature of each prompt will tend to overwrite already-established and vetted code structure, and an inexperienced user will, I suspect, eventually drown in the churn of changes produced (at scale) by the LLM.

Overview of the rest of the book

The rest of this book will examine, in some detail, the skillsets that are, in my opinion, critical to the discipline of software engineering.

Chapter 2 will re-examine the differences between programming — just writing code — and software engineering — knowing what code to write, and why.

Chapter 3 will discuss the idea of the Software Development Life Cycle (SDLC), and the kinds of activities and outcomes expected by the phases of that structure.

Chapter 4 will examine the ideas of system modeling and how the varied aspects of it shape system design and development alike.

Chapter 5 will explore the current process methodologies for software development, the paradigms that are commonly in use as code is being written, and various practices that can be leveraged to improve the speed and quality of development outcomes.

Chapter 6 will present common, standard elements of code style, documentation, and commenting, and explore when, why, and how to apply them.

Chapter 7 will focus similar examination and decision-making around functional standards for code.

Chapter 8 will show options for several common tools that help manage common Python project concerns and tasks, and some of the more common tools that are actually used to write Python code.

Chapter 9 will mark the beginning of the development story that the balance of the book is concerned with telling: the continuation of the story told in the first edition, about an imaginary company that is starting the process of moving their existing system from a collection of locally hosted services to a cloud-resident web-API-based system.

Chapter 10 will start making some decisions, in the context of the story about the project, based on information and options presented in the previous chapters.

Chapter 11 will go through the process of re-examining the existing Business Object idea in the system that is going to be rewritten, and make a decision about how the system’s data is going to be modeled and structured.

Chapter 12 will take the decisions made in Chapter 11 and determine how system data in Business Objects will be persisted to a back-end data store, after examining the options available for data storage and the criteria that relate to making those kinds of decisions.

Chapter 13 will explore the code structures needed to implement the chosen data persistence mechanism, and how to manage database changes as the needs of those Business Objects change over time.

Chapter 14 will dive into the processes needed to test the Business Objects and their data persistence processes.

Chapter 15 will explore the options available for automating the integration and deployment of code as it is written and approved for promotion to the production system.

Chapter 16 will work through the implementation needed to facilitate local development of an API system, allowing software engineers to work locally with the same code that will be deployed later into the cloud.

Chapter 17 will implement a proof-of-concept level set of API endpoints, using the Business Objects, API requirements, and local development frameworks discussed in previous chapters.

Chapter 18 will walk through the processes needed to actually deploy a functional database and API to an AWS account, leveraging AWS Infrastructure as Code tools, and exploring alternatives when and where they are needed.

Get This Book’s PDF Version and Exclusive Extras

Scan the QR code (or go to packtpub.com/unlock). Search for this book by name, confirm the edition, and then follow the steps on the page.

Note: Keep your invoice handy. Purchases made directly from Packt don’t require an invoice.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Understand what makes Software Engineering a discipline, distinct from basic programming
  • Gain practical insight into updating, refactoring, and scaling an existing Python system
  • Implement robust testing, CI/CD pipelines, and cloud-ready architecture decisions

Description

Software engineering is more than coding; it’s the strategic design and continuous improvement of systems that serve real-world needs. This newly updated second edition of Hands-On Software Engineering with Python expands on its foundational approach to help you grow into a senior or staff-level engineering role. Fully revised for today’s Python ecosystem, this edition includes updated tooling, practices, and architectural patterns. You’ll explore key changes across five minor Python versions, examine new features like dataclasses and type hinting, and evaluate modern tools such as Poetry, pytest, and GitHub Actions. A new chapter introduces high-performance computing in Python, and the entire development process is enhanced with cloud-readiness in mind. You’ll follow a complete redesign and refactor of a multi-tier system from the first edition, gaining insight into how software evolves—and what it takes to do that responsibly. From system modeling and SDLC phases to data persistence, testing, and CI/CD automation, each chapter builds your engineering mindset while updating your hands-on skills. By the end of this book, you'll have mastered modern Python software engineering practices and be equipped to revise and future-proof complex systems with confidence.

Who is this book for?

This book is for Python developers with a basic grasp of software development who want to grow into senior or staff-level engineering roles. It’s ideal for professionals looking to deepen their understanding of software architecture, system modeling, testing strategies, and cloud-aware development. Familiarity with core Python programming is required, as the book focuses on applying engineering principles to maintain, extend, and modernize real-world systems.

What you will learn

  • Distinguish software engineering from general programming
  • Break down and apply each phase of the SDLC to Python systems
  • Create system models to plan architecture before writing code
  • Apply Agile, Scrum, and other modern development methodologies
  • Use dataclasses, pydantic, and schemas for robust data modeling
  • Set up CI/CD pipelines with GitHub Actions and cloud build tools
  • Write and structure unit, integration, and end-to-end tests
  • Evaluate and integrate tools like Poetry, pytest, and Docker
Estimated delivery fee Deliver to Ireland

Premium delivery 7 - 10 business days

€23.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Dec 23, 2025
Length: 628 pages
Edition : 2nd
Language : English
ISBN-13 : 9781835888001
Category :
Languages :
Tools :

What do you get with Print?

Product feature icon Instant access to your digital copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Redeem a companion digital copy on all Print orders
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Estimated delivery fee Deliver to Ireland

Premium delivery 7 - 10 business days

€23.95
(Includes tracking information)

Product Details

Publication date : Dec 23, 2025
Length: 628 pages
Edition : 2nd
Language : English
ISBN-13 : 9781835888001
Category :
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€18.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
€189.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
€264.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts

Table of Contents

21 Chapters
Introduction Chevron down icon Chevron up icon
Programming Versus Software Engineering Revisited Chevron down icon Chevron up icon
The Software Development Life Cycle Chevron down icon Chevron up icon
System Modeling Chevron down icon Chevron up icon
Methodologies, Paradigms, and Practices Chevron down icon Chevron up icon
Code Style and Related Standards Chevron down icon Chevron up icon
Functional Code Standards Chevron down icon Chevron up icon
Revisiting Development Tools Chevron down icon Chevron up icon
Revising the hms_sys System Project Chevron down icon Chevron up icon
Updating Projects and Processes Chevron down icon Chevron up icon
Re-Examining Options for Business Objects Chevron down icon Chevron up icon
Reviewing Business Object Data Persistence Chevron down icon Chevron up icon
Data Persistence and BaseDataObject Chevron down icon Chevron up icon
Testing the Business Objects Chevron down icon Chevron up icon
CI/CD Options Chevron down icon Chevron up icon
API Options Chevron down icon Chevron up icon
Assembling the API Chevron down icon Chevron up icon
The Final API, Deployed to AWS Chevron down icon Chevron up icon
Unlock Your Exclusive Benefits Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the digital copy I get with my Print order? Chevron down icon Chevron up icon

When you buy any Print edition of our Books, you can redeem (for free) the eBook edition of the Print Book you’ve purchased. This gives you instant access to your book when you make an order via PDF, EPUB or our online Reader experience.

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact customercare@packt.com with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at customercare@packt.com using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on customercare@packt.com with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on customercare@packt.com within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on customercare@packt.com who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on customercare@packt.com within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
Modal Close icon
Modal Close icon