@@ -75,6 +75,35 @@ at least 70% coverage of the lines modified in the PR and prefer coverage
7575closer to 90%. We also require that all tests pass before a PR will be
7676merged.
7777
78+ Tests must import the Pyomo test harness from
79+ ``pyomo.common.unittest `` instead of using Python's built-in
80+ ``unittest `` module directly. This wrapper extends the standard testing
81+ framework with Pyomo-specific capabilities, including support for test
82+ timeouts and Pyomo-specific assertions for comparing expressions and
83+ nested containers with numerical tolerance. Using the provided interface
84+ ensures that all tests run consistently across Pyomo's multiple CI environments.
85+ A small example is shown below:
86+
87+ .. code-block :: python
88+
89+ import pyomo.common.unittest as unittest
90+
91+ class TestSomething (unittest .TestCase ):
92+ def test_basic (self ):
93+ self .assertEqual(1 + 1 , 2 )
94+
95+ Developers can also use any of the predefined ``pytest `` markers to categorize
96+ their tests appropriately.
97+ Markers are declared in ``pyproject.toml ``. Some commonly used markers are:
98+
99+ - ``expensive ``: tests that take a long time to run
100+ - ``mpi ``: tests that require MPI
101+ - ``solver(name) ``: dynamic marker to label a test for a specific solver,
102+ e.g., ``@pytest.mark.solver("gurobi") ``
103+
104+ More details about Pyomo-defined default test behavior can be found in
105+ the `conftest.py file <https://github.com/Pyomo/pyomo/blob/main/conftest.py >`_.
106+
78107.. note ::
79108 If you are having issues getting tests to pass on your Pull Request,
80109 please tag any of the core developers to ask for help.
@@ -336,33 +365,51 @@ Finally, move to the directory containing the clone of your Pyomo fork and run:
336365
337366::
338367
339- pip install -e .
368+ pip install -e .[tests,docs,optional]
340369
341- These commands register the cloned code with the active python environment
342- (``pyomodev ``). This way, your changes to the source code for ``pyomo `` are
370+ This command registers the cloned code with the active Python environment
371+ (``pyomodev ``) and installs all possible optional dependencies.
372+ Using ``-e `` ensures that your changes to the source code for ``pyomo `` are
343373automatically used by the active environment. You can create another conda
344374environment to switch to alternate versions of pyomo (e.g., stable).
345375
376+ .. note ::
377+
378+ The ``optional `` and ``docs `` dependencies are not strictly required;
379+ however, we recommend installing them to ensure that a large number of
380+ tests can be run locally. Optional packages that are not available will
381+ cause tests to skip.
382+
346383Review Process
347384--------------
348385
349386After a PR is opened it will be reviewed by at least two members of the
350387core development team. The core development team consists of anyone with
351- write-access to the Pyomo repository. Pull requests opened by a core
388+ write-access to the Pyomo repository. PRs opened by a core
352389developer only require one review. The reviewers will decide if they
353390think a PR should be merged or if more changes are necessary.
354391
355392Reviewers look for:
356-
357- * Outside of ``pyomo.contrib ``: Code rigor and standards, edge cases,
358- side effects, etc.
359- * Inside of ``pyomo.contrib ``: No “glaringly obvious” problems with
360- the code
361- * Documentation and tests
362393
363- The core development team tries to review pull requests in a timely
364- manner but we make no guarantees on review timeframes. In addition, PRs
365- might not be reviewed in the order they are opened in.
394+ * **Core and Addons: ** Code rigor, standards compliance, test coverage above
395+ a threshold, and avoidance of unintended side effects (e.g., regressions
396+ or backwards incompatibilities)
397+ * **Devel: ** Basic code correctness and clarity, with an understanding that
398+ these areas are experimental and evolving
399+ * **All areas: ** Code formatting (using ``black ``), documentation, and tests
400+
401+ .. note ::
402+
403+ For more information about Pyomo's development principles and the
404+ stability expectations for ``addons `` and ``devel ``, see
405+ :doc: `/principles `.
406+
407+ The core development team tries to review PRs in a timely
408+ manner, but we make no guarantees on review timeframes.
409+ Smaller, focused PRs are preferred and are generally reviewed more quickly.
410+ Larger PRs require more review effort and may take significantly longer.
411+ In addition, PRs might not be reviewed in the order in which they are opened.
412+
366413
367414Where to put contributed code
368415-----------------------------
@@ -372,64 +419,171 @@ git repository. Next, you should create a branch on your fork dedicated
372419to the development of the new feature or bug fix you're interested
373420in. Once you have this branch checked out, you can start coding. Bug
374421fixes and minor enhancements to existing Pyomo functionality should be
375- made in the appropriate files in the Pyomo code base. New examples,
376- features, and packages built on Pyomo should be placed in
377- ``pyomo.contrib ``. Follow the link below to find out if
378- ``pyomo.contrib `` is right for your code.
379-
380- ``pyomo.contrib ``
381- -----------------
382-
383- Pyomo uses the ``pyomo.contrib `` package to facilitate the inclusion
384- of third-party contributions that enhance Pyomo's core functionality.
385- The are two ways that ``pyomo.contrib `` can be used to integrate
386- third-party packages:
387-
388- * ``pyomo.contrib `` can provide wrappers for separate Python packages, thereby
389- allowing these packages to be imported as subpackages of pyomo.
390-
391- * ``pyomo.contrib `` can include contributed packages that are developed and
392- maintained outside of the Pyomo developer team.
393-
394- Including contrib packages in the Pyomo source tree provides a
422+ made in the appropriate files in the Pyomo code base.
423+
424+ We refer to the modules that form the foundation of the Pyomo environment
425+ as ``pyomo `` core. This includes the base expression systems, modeling
426+ components, model compilers, and solver interfaces. The core development
427+ team has committed to maintaining these capabilities, adhering to the
428+ strictest policies for testing and backwards compatibility.
429+
430+ Larger features, new modeling components, or experimental functionality
431+ should be placed in one of Pyomo's extension namespaces, described below.
432+
433+ Namespaces for Contributed and Experimental Code
434+ ++++++++++++++++++++++++++++++++++++++++++++++++
435+
436+ Pyomo organizes non-core functionality into a small
437+ number of clearly defined namespaces. Contributors should place new
438+ functionality according to its intended stability and maintenance
439+ expectations:
440+
441+ * ``pyomo.addons `` – For mostly stable, supported extensions that build on
442+ the Pyomo core. These packages are maintained by dedicated
443+ contributors, follow Pyomo's coding and testing standards, and adhere
444+ to the same backwards compatibility and deprecation policies as the
445+ rest of the codebase.
446+
447+ * ``pyomo.devel `` – For experimental or rapidly evolving
448+ contributions. These modules serve as early experimentation for research ideas,
449+ prototypes, or specialized modeling components. Functionality under
450+ this namespace may change or be removed between releases without
451+ deprecation warnings.
452+
453+ * ``pyomo.unsupported `` - For contributions that no longer have an active
454+ maintainer nor any future development plans. Functionality under this namespace
455+ may not work and is **NOT ** tested through the standard test harness.
456+
457+ This tiered namespace structure provides contributors a clear pathway from
458+ **experimentation to supported integration **, while protecting users from
459+ unexpected changes in stable areas of the codebase.
460+
461+ .. list-table ::
462+ :header-rows: 1
463+ :widths: 20 30 50
464+
465+ * - Namespace
466+ - Intended Use
467+ - Stability
468+ * - ``pyomo.devel ``
469+ - Active research and experimental code
470+ - Unstable; APIs may change without warning
471+ * - ``pyomo.addons ``
472+ - Mostly stable, supported extensions maintained by contributors
473+ - Mostly stable APIs; follow Pyomo's standards
474+ * - ``pyomo.unsupported ``
475+ - Unsupported, unmaintained code
476+ - No guarantee of functionality and no regular testing
477+ * - ``pyomo ``
478+ - Core Pyomo modeling framework
479+ - Fully supported and versioned
480+
481+ Submitting a Contributed Package
482+ --------------------------------
483+
484+ Including contributed packages in the Pyomo source tree provides a
395485convenient mechanism for defining new functionality that can be
396- optionally deployed by users. We expect this mechanism to include
397- Pyomo extensions and experimental modeling capabilities. However,
398- contrib packages are treated as optional packages, which are not
399- maintained by the Pyomo developer team. Thus, it is the responsibility
486+ optionally deployed by users. We expect this mechanism to include
487+ Pyomo extensions and experimental modeling capabilities. However,
488+ contributed packages are treated as optional packages, which are not necessarily
489+ maintained by the Pyomo developer team. Thus, it is the responsibility
400490of the code contributor to keep these packages up-to-date.
401491
402- Contrib package contributions will be considered as pull- requests,
403- which will be reviewed by the Pyomo developer team. Specifically,
492+ Contributed packages will be considered as pull requests,
493+ which will be reviewed by the Pyomo developer team. Specifically,
404494this review will consider the suitability of the proposed capability,
405495whether tests are available to check the execution of the code, and
406496whether documentation is available to describe the capability.
407- Contrib packages will be tested along with Pyomo. If test failures
497+ Contributed packages will be tested along with Pyomo. If test failures
408498arise, then these packages will be disabled and an issue will be
409- created to resolve these test failures.
499+ created to resolve these test failures. The Pyomo team reserves the
500+ right to remove contributed packages that are not maintained.
501+
502+ When submitting a new package (under either ``addons `` or
503+ ``devel ``), please ensure that:
504+
505+ * The package has at least one maintainer responsible for its upkeep.
506+ * The code includes tests that can be run through Pyomo's
507+ continuous integration framework.
508+ * The package includes documentation that clearly describes its purpose and
509+ usage, preferably as online documentation in ``doc/OnlineDocs ``.
510+ * Optional dependencies are properly declared in ``setup.py ``
511+ under the appropriate ``[optional] `` section.
512+ * The contribution passes all standard style and formatting checks.
513+
514+ Example: Structure of a Contributed Package
515+ -------------------------------------------
516+
517+ This section illustrates a minimal example of how a contributed package
518+ may be structured within the ``pyomo.devel `` or ``pyomo.addons ``
519+ namespaces. This example is provided for documentation purposes only
520+ and is not included as source code in the Pyomo repository.
521+
522+ Minimal Directory Layout
523+ ++++++++++++++++++++++++
524+
525+ At a minimum, a contributed package should follow a structure similar
526+ to the following::
527+
528+ pyomo/devel/example_package/
529+ ├── __init__.py
530+ ├── core.py
531+ └── tests/
532+ ├── __init__.py
533+ └── test_example_package.py
534+
535+ Package Initialization
536+ ++++++++++++++++++++++
537+
538+ The package ``__init__.py `` file should expose the primary public
539+ interfaces of the package and avoid unnecessary imports. Contributed
540+ packages must be safe to import as optional components and should not
541+ introduce side effects at import time.
542+
543+ For example::
544+
545+ # pyomo/devel/example_package/__init__.py
546+ from pyomo.devel.example_package.core import example_function
547+
548+ Core Functionality
549+ ++++++++++++++++++
550+
551+ The main functionality of the contributed package should be implemented
552+ in one or more modules within the package directory (for example,
553+ ``core.py ``). These modules should follow Pyomo's coding standards,
554+ documentation requirements, and dependency management policies.
410555
411- Contrib Packages within Pyomo
412- +++++++++++++++++++++++++++++
556+ Tests
557+ +++++
413558
414- Third-party contributions can be included directly within the
415- ``pyomo.contrib `` package. The ``pyomo/contrib/example `` package
416- provides an example of how this can be done, including a directory
417- for plugins and package tests. For example, this package can be
418- imported as a subpackage of ``pyomo.contrib ``::
559+ All contributed packages must include tests. Tests should be placed in a
560+ ``tests `` subpackage and use the Pyomo test harness provided by
561+ ``pyomo.common.unittest ``.
419562
420- import pyomo.environ as pyo
421- from pyomo.contrib.example import a
563+ At a minimum, tests should verify that the package can be imported and
564+ that its primary functionality executes as expected. For example::
422565
423- # Print the value of 'a' defined by this package
424- print(a)
566+ import pyomo.common.unittest as unittest
425567
426- Although ``pyomo.contrib.example `` is included in the Pyomo source
427- tree, it is treated as an optional package. Pyomo will attempt to
428- import this package, but if an import failure occurs, Pyomo will
429- silently ignore it. Otherwise, this pyomo package will be treated
430- like any other. Specifically:
568+ class TestExamplePackage(unittest.TestCase):
569+ def test_import(self):
570+ import pyomo.devel.example_package
431571
432- * Plugin classes defined in this package are loaded when ``pyomo.environ `` is loaded.
572+ Tests for contributed packages are run as part of the Pyomo
573+ test suite and must not have an unconditional import of optional dependencies.
574+ Tests that exercise functionality requiring optional dependencies must be
575+ properly guarded (e.g., with ``@unittest.skipIf() `` / ``@unittest.skipUnless() ``).
576+ Pyomo provides a standard tool for supporting the delayed import of optional
577+ dependencies (see :py: `attempt_import() `) as well as a central location for
578+ importing many common optional dependencies (see :py:mod: `pyomo.common.dependencies `).
579+ For example, tests that require ``numpy `` may be marked using the Pyomo
580+ test harness as follows::
433581
434- * Tests in this package are run with other Pyomo tests.
582+ import pyomo.common.unittest as unittest
583+ from pyomo.common.dependencies import numpy as np, numpy_available
435584
585+ @unittest.skipIf(not numpy_available, "NumPy is not available")
586+ class TestExampleWithNumpy(unittest.TestCase):
587+ def test_numpy_functionality(self):
588+ a = np.array([1, 2, 3])
589+ self.assertEqual(a.sum(), 6)
0 commit comments