Repository: joke2k/faker
Pull request #2138 (2024-11-14 19:43:14)

Resolve address number output issue and refactor road_formats for clarity
# What does this change
This change addresses the issue in the previous implementation (#2133), where `#` was mistakenly printed instead of the actual address numbers. Additionally, the `road_formats` structure has been simplified by incorporating `{{building_number}}` directly for more concise formatting.

# What was wrong
The original code had a formatting error where `#` was printed instead of the intended numeric address numbers. This caused unexpected outputs in the feature responsible for generating ROK (🇰🇷) road addresses (도로명 주소), leading to inaccuracies.

# How this fixes it
The building_number method now correctly handles the generation of building numbers. It produces a standard building number or, with a certain probability, generates variations such as underground entrances using `building_number_underground()` or `includes sub-building numbers via building_number_segregated()`. This probability was determined arbitrarily, as there are no official government statistics available for this data.

### Checklist

- [x] I have read the documentation about [CONTRIBUTING](https://github.com/joke2k/faker/blob/master/CONTRIBUTING.rst)
- [x] I have read the documentation about [Coding style](https://github.com/joke2k/faker/blob/master/docs/coding_style.rst)
- [x] I have run `make lint`


stefan6419846: As this is a bug of the previous variant, shouldn't there be a test which checks for the previously broken behavior as well?
semi-yu: Hello,
You mentioned that it was a bug. However, there are a few points to consider:

1. It's not so much a bug as it was my oversight. I forgot to add the `bothify()` function. 
2. This implementation fixes the part that was done clumsily. Instead of relying on adding strings to formats, I changed it so that the function generates the string.
3. For string returns, I ensured that only `str` types are returned and modified the tests to check only for that.

Please review these changes. Since I'm using ChatGPT for communication, there may be some nuances lost in translation. Thank you.
fcurella: If addresses are supposed to never contain `#`, you could write a test like `assert "#" not in building_number`
semi-yu: Good morning from Korea.

Thank you for clarifying the task.
I have added two test cases for the newly implemented building number functions.

- Any string generated by `building_number_underground()` must begin with '지하' (which means "underground").
- Any string generated by `building_number_segregated()` must include a dash ('-').

Please let me know if anything else is needed!

Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 85de310d..bfd72848 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 33.0.0
+current_version = 33.1.0
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e6e915bc..54f185f8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -138,7 +138,13 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+        python:
+        - "3.8"
+        - "3.9"
+        - "3.10"
+        - "3.11"
+        - "3.12"
+        - "3.13"
 
     steps:
     - uses: actions/checkout@v4
@@ -168,7 +174,14 @@ jobs:
     needs: [flake8, isort, black, doc8, checkmanifest, typing]
     strategy:
       matrix:
-        python: ["3.8", "3.9", "3.10", "3.11", "3.12", "pypy-3.10"]
+        python:
+        - "3.8"
+        - "3.9"
+        - "3.10"
+        - "3.11"
+        - "3.12"
+        - "3.13"
+        - "pypy-3.10"
 
     steps:
       - uses: actions/checkout@v4
@@ -209,7 +222,13 @@ jobs:
     needs: [flake8, isort, black, doc8, checkmanifest, typing]
     strategy:
       matrix:
-        python: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+        python:
+        - "3.8"
+        - "3.9"
+        - "3.10"
+        - "3.11"
+        - "3.12"
+        - "3.13"
         arch: ["x86", "x64"]
 
     steps:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 52ef14e7..589edea3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 ## Changelog
 
+### [v33.1.0 - 2024-11-27](https://github.com/joke2k/faker/compare/v33.0.0...v33.1.0)
+
+* Add support for Python 3.13. Thanks @edgarrmondragon.
+
 ### [v33.0.0 - 2024-11-14](https://github.com/joke2k/faker/compare/v32.1.0...v33.0.0)
 
 * Revert "Make pytest fixture session-scoped".
diff --git a/Makefile b/Makefile
index 6ba83f52..1e6a9442 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@ isort:
 	isort --atomic .
 
 generate-stubs: 
-	python generate_stubs.py
+	python3.10 generate_stubs.py
 
 lint: generate-stubs isort black mypy flake8
 
diff --git a/VERSION b/VERSION
index eec9af7d..4369e02e 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-33.0.0
+33.1.0
diff --git a/docs/conf.py b/docs/conf.py
index 7c3ef91e..3a06ab45 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -51,9 +51,9 @@ copyright = f"2014-{datetime.now().year}, Daniele Faraglia"
 # built documents.
 #
 # The short X.Y version.
-version = "33.0.0"
+version = "33.1.0"
 # The full version, including alpha/beta/rc tags.
-release = "33.0.0"
+release = "33.1.0"
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/faker/__init__.py b/faker/__init__.py
index a0f673c7..97c84e14 100644
--- a/faker/__init__.py
+++ b/faker/__init__.py
@@ -2,6 +2,6 @@ from faker.factory import Factory
 from faker.generator import Generator
 from faker.proxy import Faker
 
-VERSION = "33.0.0"
+VERSION = "33.1.0"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/address/ko_KR/__init__.py b/faker/providers/address/ko_KR/__init__.py
index 437a2c00..5d67409d 100644
--- a/faker/providers/address/ko_KR/__init__.py
+++ b/faker/providers/address/ko_KR/__init__.py
@@ -454,12 +454,8 @@ class Provider(AddressProvider):
         "{{building_name}} {{building_dong}}동 ###호",
     )
     road_formats = (
-        "{{road_name}}{{road_suffix}} ###",
-        "{{road_name}}{{road_suffix}} 지하###",
-        "{{road_name}}{{road_suffix}} ###-##",
-        "{{road_name}}{{road_number}}{{road_suffix}} ###",
-        "{{road_name}}{{road_number}}{{road_suffix}} 지하###",
-        "{{road_name}}{{road_number}}{{road_suffix}} ###-##",
+        "{{road_name}}{{road_suffix}} {{building_number}}",
+        "{{road_name}}{{road_number}}{{road_suffix}} {{building_number}}",
     )
     road_address_formats = (
         "{{metropolitan_city}} {{borough}} {{road}}",
@@ -531,6 +527,44 @@ class Provider(AddressProvider):
         """
         return self.random_element(self.road_suffixes)
 
+    def building_number(self) -> str:
+        """
+        :returns: A random building number
+
+        Generates building number(건물 번호). There are 3 types of building number with current ROK addressing system.
+            (1) 19: A typical format. Only marks one building.
+            (2) 지하11: The building entrance is underground.
+            (3) 132-1: Several buildings are distinguished with sub-building-number(가지 번호).
+
+        Generating probability is arbitrarily.
+
+        :example: 19, 지하11, 143-1
+        """
+        if self.random_int() % 9 < 1:
+            return self.building_number_underground()
+        elif self.random_int() % 9 < 4:
+            return self.building_number_segregated()
+        else:
+            return "%d" % self.generator.random.randint(1, 999)
+
+    def building_number_underground(self) -> str:
+        """
+        :returns: A random building number with undergrond entrances
+
+        :example: 지하11
+        """
+        return "지하%d" % (self.generator.random.randint(1,999))
+
+    def building_number_segregated(self) -> str:
+        """
+        :returns: A random building number distinguished with sub-building-number(가지 번호)
+
+        :example: 143-1
+        """
+        main_building_number = self.generator.random.randint(1, 999)
+        sub_building_number = self.generator.random.randint(1, 99)
+        return "%d-%d" % (main_building_number, sub_building_number)
+
     def metropolitan_city(self) -> str:
         """
         :example: 서울특별시
diff --git a/faker/providers/misc/__init__.py b/faker/providers/misc/__init__.py
index 287c3065..7c67b5e2 100644
--- a/faker/providers/misc/__init__.py
+++ b/faker/providers/misc/__init__.py
@@ -20,7 +20,7 @@ from ..python import TypesSpec
 
 localized = True
 
-csv.register_dialect("faker-csv", csv.excel, quoting=csv.QUOTE_ALL)
+csv.register_dialect("faker-csv", csv.excel, quoting=csv.QUOTE_ALL)  # type: ignore
 
 
 ColumnSpec: TypeAlias = Union[Tuple[int, str], Tuple[int, str, Dict[str, Any]]]
diff --git a/setup.py b/setup.py
index eae039eb..97b13c1d 100644
--- a/setup.py
+++ b/setup.py
@@ -43,6 +43,7 @@ setup(
         "Programming Language :: Python :: 3.10",
         "Programming Language :: Python :: 3.11",
         "Programming Language :: Python :: 3.12",
+        "Programming Language :: Python :: 3.13",
         "Programming Language :: Python :: Implementation :: CPython",
         "Programming Language :: Python :: Implementation :: PyPy",
         "Topic :: Software Development :: Libraries :: Python Modules",
diff --git a/tox.ini b/tox.ini
index 84dc3808..8e435672 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,11 @@
 [tox]
-envlist=py{38,39,310,311,312,py3},alpine,flake8,checkmanifest,isort,mypy,doc8
+envlist=py{38,39,310,311,312,313,py3},alpine,flake8,checkmanifest,isort,mypy,doc8
 skip_missing_interpreters = true
 
 [testenv]
 deps =
     coverage>=5.2
-    freezegun<0.4
+    freezegun
     pytest>=6.0.1
     ukpostcodeparser>=1.1.1
     validators>=0.13.0


Test patch:
diff --git a/tests/providers/test_address.py b/tests/providers/test_address.py
index a5101324..d677bffb 100644
--- a/tests/providers/test_address.py
+++ b/tests/providers/test_address.py
@@ -1305,6 +1305,26 @@ class TestKoKr:
             building_name = faker.building_name()
             assert isinstance(building_name, str)
 
+    def test_building_number(self, faker, num_samples):
+        for _ in range(num_samples):
+            building_number = faker.building_number()
+            assert isinstance(building_number, str)
+            assert '#' not in building_number
+
+    def test_building_number_underground(self, faker, num_samples):
+        for _ in range(num_samples):
+            building_number = faker.building_number_underground()
+            assert isinstance(building_number, str)
+            assert '#' not in building_number
+            assert building_number[:2] == '지하'
+
+    def test_building_number_segregated(self, faker, num_samples):
+        for _ in range(num_samples):
+            building_number = faker.building_number_segregated()
+            assert isinstance(building_number, str)
+            assert '#' not in building_number
+            assert '-' in building_number
+
     def test_building_suffix(self, faker, num_samples):
         for _ in range(num_samples):
             building_suffix = faker.building_suffix()